Exception de-serializing SOAP XML into Object - c#

I am developing a WCF Service that can accept and return SOAP 1.1 messages. However, I am having problems deserializing the SOAP body to my object. At the moment I have;
namespace MyAPI
{
[ServiceContract]
public interface IMyService
{
[OperationContract(IsOneWay = false, Action = "*", ReplyAction = "*")]
[WebInvoke(Method = "POST",
BodyStyle = WebMessageBodyStyle.Bare, RequestFormat=WebMessageFormat.Xml)]
Message ProcessMessgae(Message message);
}
}
public class MyService : IMyService
{
public Message ProcessMessgae(Message message)
{
MessageBuffer buffer = message.CreateBufferedCopy(8192);
// Get a copy of the original message.
Message msgCopy = buffer.CreateMessage();
// Take another copy of the same message.
Message returnMsg = buffer.CreateMessage();
// Use the msgCopy to get an XML and extract the body contents. Once this message has been read and consumed,
System.Xml.XmlDictionaryReader xrdr = msgCopy.GetReaderAtBodyContents();
string bodyData = xrdr.ReadOuterXml();
var reader = new StringReader(bodyData);
var serializer = new XmlSerializer(typeof(OTA_HotelAvailRQ));
var instance = (OTA_HotelAvailRQ)serializer.Deserialize(reader);
return returnMsg;
}
}
This is my auto-generated object
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.2046.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.opentravel.org/OTA/2003/05")]
public partial class OTA_HotelAvailRQ : object, System.ComponentModel.INotifyPropertyChanged
{
private string echoTokenField;
private System.DateTime timeStampField;
private bool timeStampFieldSpecified;
private bool targetFieldSpecified;
private decimal versionField;
private bool availRatesOnlyField;
private bool availRatesOnlyFieldSpecified;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string EchoToken
{
get
{
return this.echoTokenField;
}
set
{
this.echoTokenField = value;
this.RaisePropertyChanged("EchoToken");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public System.DateTime TimeStamp
{
get
{
return this.timeStampField;
}
set
{
this.timeStampField = value;
this.RaisePropertyChanged("TimeStamp");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool TimeStampSpecified
{
get
{
return this.timeStampFieldSpecified;
}
set
{
this.timeStampFieldSpecified = value;
this.RaisePropertyChanged("TimeStampSpecified");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public decimal Version
{
get
{
return this.versionField;
}
set
{
this.versionField = value;
this.RaisePropertyChanged("Version");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public bool AvailRatesOnly
{
get
{
return this.availRatesOnlyField;
}
set
{
this.availRatesOnlyField = value;
this.RaisePropertyChanged("AvailRatesOnly");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool AvailRatesOnlySpecified
{
get
{
return this.availRatesOnlyFieldSpecified;
}
set
{
this.availRatesOnlyFieldSpecified = value;
this.RaisePropertyChanged("AvailRatesOnlySpecified");
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null))
{
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
However, when I run the code I'm getting an exception on the following line var instance = (OTA_HotelAvailRQ)serializer.Deserialize(reader);
The SOAP XML I'm posting is;
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<wsse:Security soap:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<wsse:UsernameToken>
<wsse:Username>test</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">thebest</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<OTA_HotelAvailRQ xmlns="http://www.opentravel.org/OTA/2003/05" Version="1.0" TimeStamp="2005-08-01T09:30:47+02:00" EchoToken="fb57388d" AvailRatesOnly="true">
</OTA_HotelAvailRQ>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The actual exception is;

What happens if you add your namespace to the xmlserializer?
var instance = (OTA_HotelAvailRQ)serializer.Deserialize(reader, "http://www.opentravel.org/OTA/2003/05");
Another option would be do something like the below, you may have to ReadOuterXml() instead of ReadInnerXml, the namespace may be able to be removed from this method too:
System.Xml.Linq.XElement body = System.Xml.Linq.XElement.Parse(msgCopy.GetReaderAtBodyContents().ReadInnerXml());
var instance = Deserialize<OTA_HotelAvailRQ>(body, "http://www.opentravel.org/OTA/2003/05");
public static T Deserialize<T>(XElement xElement, string nameSpace)
{
using (MemoryStream memoryStream = new MemoryStream(Encoding.ASCII.GetBytes(xElement.ToString())))
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T), nameSpace);
return (T)xmlSerializer.Deserialize(memoryStream);
}
}
If you are still having problems, there is also a way using the DataContractSerializer or a SoapreflectionImporter, but you don't have a very complex object there those may be overkill.

Related

Create SecurityHeaderType object with wsse:usernameToken and wsu:Timestamp

i can't get my security header right in my SOAP web service call.
Here's what i need in XML as a result
<wsse:Security
xmlns:wsse="url_preserved"
xmlns:wsu="url_preserved">
<wsse:UsernameToken wsu:Id="A3380A376973ABE6A6150840400087524">
<wsse:Username>username</wsse:Username>
<wsse:Password Type="wsse:PasswordDigest">nuQbmp5F6bWH5AlQRLRRxWonQ=</wsse:Password>
<wsse:Nonce>aaa3aSJqORu6x62BdNNczw==</wsse:Nonce>
<wsu:Created>2017-10-19T08:44:51Z</wsu:Created>
</wsse:UsernameToken>
<wsu:Timestamp wsu:Id="TS-A3380A376973ABE6A6150840400087423">
<wsu:Created>2017-10-19T09:10:40.874Z</wsu:Created>
<wsu:Expires>2017-10-19T09:12:40.874Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
My Webservice reference security object is SecurityHeaderType and am trying to set it up right like this :
Class SecurityHeaderType
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.8.4084.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" +
"")]
public partial class SecurityHeaderType : object, System.ComponentModel.INotifyPropertyChanged {
private System.Xml.XmlElement[] anyField;
private System.Xml.XmlAttribute[] anyAttrField;
/// <remarks/>
[System.Xml.Serialization.XmlAnyElementAttribute(Order=0)]
public System.Xml.XmlElement[] Any {
get {
return this.anyField;
}
set {
this.anyField = value;
this.RaisePropertyChanged("Any");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAnyAttributeAttribute()]
public System.Xml.XmlAttribute[] AnyAttr {
get {
return this.anyAttrField;
}
set {
this.anyAttrField = value;
this.RaisePropertyChanged("AnyAttr");
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null)) {
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
Generation method
private static SecurityHeaderType GetSecurityHeader()
{
var doc = new XmlDocument();
var security = doc.CreateElement("wsse","Security", "url_preserved");
XmlElement usernameTokenChild = doc.CreateElement("wsse", "UsernameToken", "url_preserved");
var usernameToken = security.AppendChild(usernameTokenChild);
var username = usernameToken.AppendChild(doc.CreateElement("wsse","Username", "url_preserved" ));
username.InnerText = "username";
XmlElement passwordElement = doc.CreateElement("wsse", "Password", "url_preserved");
passwordElement.SetAttribute("Type", "wsse:PasswordDigest");
var password = usernameToken.AppendChild(passwordElement);
password.InnerText = "nuQbmp5F6bWH5AlQRLRRxWonQ=";
XmlElement createdElement = doc.CreateElement("wsu","Created", "url_preserved");
var created = usernameToken.AppendChild(createdElement);
created.InnerText = DateTime.Now.ToString();
XmlElement timestampChild = doc.CreateElement("wsu", "Timestamp", "url_preserved");
var timestamp = security.AppendChild(timestampChild);
var createdTimestamp = timestamp.AppendChild(doc.CreateElement("Created"));
createdTimestamp.InnerText = DateTime.Now.ToString();
var expiresTimestamp= timestamp.AppendChild(doc.CreateElement("Expires"));
expiresTimestamp.InnerText = DateTime.Now.AddSeconds(60).ToString();
SecurityHeaderType securityHeaderType = new SecurityHeaderType();
securityHeaderType.Any = new System.Xml.XmlElement[2];
securityHeaderType.Any[0] = usernameTokenChild;
securityHeaderType.Any[1]= timestampChild;
return securityHeaderType;
}
I get UnauthenticatedException from the webservice indicating that The WS-Security header is not correctly setup.
I have to say that it's my first try on WS on C#. I don't feel at ease at all when i setup the security header.
I did try many ways to implement correctly my SecurityHeaderType object. without success so far.
any hints ?

How to remove extra result tag from my SOAP response

I know this questions has been asked before but I could not find my answer anywhere.
the thing is I have below code on my asmx file:
namespace IrancellSmsServer
{
[SoapDocumentService(RoutingStyle = SoapServiceRoutingStyle.RequestElement)]
[WebService(Namespace = "http://www.csapi.org/schema/parlayx/data/sync/v1_0/local")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class SoapServer : System.Web.Services.WebService
{
[WebMethod]
public syncOrderRelationResponse syncOrderRelation(
Sync.UserID userID,
string spID,
string productID,
string serviceID,
string serviceList,
int updateType,
string updateTime,
string updateDesc,
string effectiveTime,
string expiryTime,
item[] extensionInfo
)
{
syncOrderRelationResponse a = new syncOrderRelationResponse();
a.result = 0;
a.resultDescription = "OK";
return a;
}
}
}
And this is the result:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<syncOrderRelationResponse xmlns="http://www.csapi.org/schema/parlayx/data/sync/v1_0/local">
<syncOrderRelationResult> //dont want this
<result>0</result>
<resultDescription>OK</resultDescription>
</syncOrderRelationResult> //dont want this
</syncOrderRelationResponse>
</soap:Body>
</soap:Envelope
the problem is I dont want that extra <syncOrderRelationResult> tag that .net created automatically. I want it completely remove. I have tried [SoapDocumentMethod(ParameterStyle=SoapParameterStyle.Bare)]
but no luck it tels me I should have one parameter. is there any way I could do this?
expected result:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<syncOrderRelationResponse xmlns="http://www.csapi.org/schema/parlayx/data/sync/v1_0/local">
<result>0</result>
<resultDescription>OK</resultDescription>
</syncOrderRelationResponse>
</soap:Body>
</soap:Envelope
here is the code of syncOrderRelationResponse:
namespace IrancellSmsServer.Sync {
using System;
using System.Web.Services;
using System.Diagnostics;
using System.Web.Services.Protocols;
using System.Xml.Serialization;
using System.ComponentModel;
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Web.Services.WebServiceBindingAttribute(Name="DataSyncBinding", Namespace="http://www.csapi.org/wsdl/parlayx/data/sync/v1_0/service")]
public partial class DataSyncService : System.Web.Services.Protocols.SoapHttpClientProtocol {
private bool useDefaultCredentialsSetExplicitly;
/// <remarks/>
public DataSyncService() {
this.Url = global::IrancellSmsServer.Properties.Settings.Default.IrancellSmsServer_Sync_DataSyncService;
if ((this.IsLocalFileSystemWebService(this.Url) == true)) {
this.UseDefaultCredentials = true;
this.useDefaultCredentialsSetExplicitly = false;
}
else {
this.useDefaultCredentialsSetExplicitly = true;
}
}
public new string Url {
get {
return base.Url;
}
set {
if ((((this.IsLocalFileSystemWebService(base.Url) == true)
&& (this.useDefaultCredentialsSetExplicitly == false))
&& (this.IsLocalFileSystemWebService(value) == false))) {
base.UseDefaultCredentials = false;
}
base.Url = value;
}
}
public new bool UseDefaultCredentials {
get {
return base.UseDefaultCredentials;
}
set {
base.UseDefaultCredentials = value;
this.useDefaultCredentialsSetExplicitly = true;
}
}
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Bare)]
[return: System.Xml.Serialization.XmlElementAttribute("syncOrderRelationResponse", Namespace="http://www.csapi.org/schema/parlayx/data/sync/v1_0/local")]
public syncOrderRelationResponse syncOrderRelation([System.Xml.Serialization.XmlElementAttribute("syncOrderRelation", Namespace="http://www.csapi.org/schema/parlayx/data/sync/v1_0/local")] syncOrderRelation syncOrderRelation1) {
object[] results = this.Invoke("syncOrderRelation", new object[] {
syncOrderRelation1});
return ((syncOrderRelationResponse)(results[0]));
}
/// <remarks/>
public void syncOrderRelationAsync(syncOrderRelation syncOrderRelation1, object userState) {
if ((this.syncOrderRelationOperationCompleted == null)) {
this.syncOrderRelationOperationCompleted = new System.Threading.SendOrPostCallback(this.OnsyncOrderRelationOperationCompleted);
}
this.InvokeAsync("syncOrderRelation", new object[] {
syncOrderRelation1}, this.syncOrderRelationOperationCompleted, userState);
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1055.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.csapi.org/schema/parlayx/data/sync/v1_0/local")]
public partial class syncOrderRelationResponse {
private int resultField;
private string resultDescriptionField;
//private item[] extensionInfoField;
/// <remarks/>
public int result {
get {
return this.resultField;
}
set {
this.resultField = value;
}
}
/// <remarks/>
public string resultDescription {
get {
return this.resultDescriptionField;
}
set {
this.resultDescriptionField = value;
}
}
/// <remarks/>
//[System.Xml.Serialization.XmlArrayItemAttribute("item", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=false)]
//public item[] extensionInfo {
// get {
// return this.extensionInfoField;
// }
// set {
// this.extensionInfoField = value;
// }
//}
}
/// <remarks/>
public syncOrderRelationResponse Result {
get {
this.RaiseExceptionIfNecessary();
return ((syncOrderRelationResponse)(this.results[0]));
}
}
}
}
}
Add this on top of your method
[WebMethod]
[return: XmlElement("syncOrderRelationResponse")]
[SoapDocumentMethod(ParameterStyle = SoapParameterStyle.Bare)]
Add this on top of your method
[WebMethod]
[return: XmlElement("syncOrderRelationResponse")]
[SoapDocumentMethod(ParameterStyle = SoapParameterStyle.Bare)]
and change it from:
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
to :
[WebServiceBinding(ConformsTo = WsiProfiles.None)]
and You can also use this link : WOOw!
https://stackoverflow.com/a/29855766/8517391
I had the same requirement and after digging for a while I came with a workaround as follows:
As mentioned before, in order to remove the result tag, do the following:
[WebMethod]
[return: XmlElement("syncOrderRelationResponse")]
[SoapDocumentMethod(ParameterStyle = SoapParameterStyle.Bare)]
//the syncOrderRelation method goes here ...
This will result in the output as follows:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<syncOrderRelationResponse
xmlns="http://www.csapi.org/schema/parlayx/data/sync/v1_0/local">
<result>0</result>
<resultDescription>OK</resultDescription>
</syncOrderRelationResponse>
</soap:Body>
</soap:Envelope
as you desired. However, It will also strip the method name syncOrderRelation in the request because ParameterStyle is applied to both request and response elements.
Workaround:
Create a Class named syncOrderRelation and put all the parameters from syncOrderRelation request method into the class you just created and use an Object of syncOrderRelation class as a parameter for the method.
Done!
implementing SoapCore (like wcf)
in my case
write this
[MessageContract(IsWrapped = false)]
to returned class head and write this
[return: XmlElement("availabilityResponse")]
to adapter class head
It is not possible to remove this result. At least it is not documented.
However you can modify this name via the return attribute:
I know this is may not be directly helpful for you (as you want to flatten the structure), but since this is not really a standard way, this can be helpful anyway to know that you can at least modify the name of the result tag.
If you want to change <syncOrderRelationResult> to <carResult>, just add [XmlRoot("carResult")] to your object, not your function / method. Here it's sample :
[XmlRoot("carResult")]
public class ObjectCar
{
public string model;
public string color;
}
or add [return: System.Xml.Serialization.XmlElementAttribute("carResult")] to your function / method :
[WebMethod]
[return: System.Xml.Serialization.XmlElementAttribute("carResult")]
public string syncOrderRelation(string a)
{
return a;
}
If you want to change <syncOrderRelationResponse ..> to <YourFunctionName ..>, just add [SoapDocumentMethodAttribute(ResponseElementName = "YourFunctionName")] to your function / method :
[WebMethod]
[return: System.Xml.Serialization.XmlElementAttribute("carResult")][SoapDocumentMethodAttribute(ResponseElementName = "YourFunctionName")]
public string syncOrderRelation(string a)
{
return a;
}
hope it's help

Deserializing XML gives me null instead of the object

This is my XML:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE paymentService PUBLIC "-//WorldPay//DTD WorldPay PaymentService v1//EN"
"http://dtd.worldpay.com/paymentService_v1.dtd">
<paymentService version="1.4" merchantCode="ABC">
<reply>
<error code="4">
<![CDATA[Security violation]]>
</error>
</reply>
</paymentService>
I'm deserializing it into classes created using the XSD they provided:
var responseStreamReader = new StreamReader(response.GetResponseStream());
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "paymentService";
XmlSerializer mySerializer = new XmlSerializer(typeof(paymentService), xRoot);
var someResponse = (paymentService) mySerializer.Deserialize(responseStreamReader);
It deserializes the paymentService, version, merchantCode, but the Item property is null.
This is part of the schema:
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.33440")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://tempuri.org/worldpa
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://tempuri.org/worldpay", IsNullable = false
public partial class paymentService
{
private object itemField;
private paymentServiceVersion versionField;
private string merchantCodeField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("inquiry", typeof (inquiry))]
[System.Xml.Serialization.XmlElementAttribute("modify", typeof (modify))]
[System.Xml.Serialization.XmlElementAttribute("notify", typeof (notify))]
[System.Xml.Serialization.XmlElementAttribute("reply", typeof (reply))]
[System.Xml.Serialization.XmlElementAttribute("submit", typeof (submit))]
[System.Xml.Serialization.XmlElementAttribute("verify", typeof (verify))]
public object Item
{
get { return this.itemField; }
set { this.itemField = value; }
}
/// <remarks/>
[System.Xml.Serialization.XmlAttribute]
public paymentServiceVersion version
{
get { return this.versionField; }
set { this.versionField = value; }
}
/// <remarks/>
[System.Xml.Serialization.XmlAttribute(DataType = "NMTOKEN")]
public string merchantCode
{
get { return this.merchantCodeField; }
set { this.merchantCodeField = value; }
}
}
I would expect the Item to be a reply object.
What could I be doing wrong?
I've fixed this, possibly not in the best way, but for the benefit of others..
I just removed the namespaces from everything.
My schema was generated with XSD.exe and I removed all references to Namespace = "http://tempuri.org/worldpay", and this fixed it. It also meant I didn't have to mess around with the XmlRootAttribute.

Class for serialization into XML with different node structure in list

I want to create a serializable C# class that would serialize into:
<metadata>
<entry key="">
<dimensionInfo>
<enabled>false</enabled>
</dimensionInfo>
</entry>
<entry key="">false</entry>
</metadata>
Can you help me? I can't handle different entry node structure:/ Too hard for me:P
Let's begin from the lowest class in the hierarchy:
[Serializable]
public class DimensionInfo
{
[XmlElement("enabled")]
public Boolean Enabled { get; set; }
public DimensionInfo()
{
}
}
As you see, there's nothing special here. Then, proceed with the next:
[Serializable]
public class Entry
{
private DimensionInfo _dimensionInfo = default(DimensionInfo);
private Boolean _containsDimensionInfo = true;
[XmlAttribute("key")]
public String Key { get; set; }
[XmlText(typeof(String))]
public String ContainsDimensionInfo
{
get
{
return CheckDimensionContaining().ToString().ToLower();
}
set
{
_containsDimensionInfo = Boolean.Parse(value);
}
}
[XmlIgnore]
public Boolean ContainsDimensionInfoSpecified
{
get { return !CheckDimensionContaining(); }
}
[XmlElement("dimensionInfo")]
public DimensionInfo DimensionInfo
{
get { return _dimensionInfo; }
set { _dimensionInfo = value; }
}
[XmlIgnore]
public Boolean DimensionInfoSpecified
{
get { return CheckDimensionContaining(); }
}
public Entry()
{
Key = String.Empty;
CheckDimensionContaining();
}
private Boolean CheckDimensionContaining()
{
return _containsDimensionInfo = _dimensionInfo != default(DimensionInfo);
}
}
Here the magic begins. With using selective XmlIgnoreAttribute we can decide the ways the object serializes. The main class only wraps a list of Entries:
[Serializable]
[XmlRoot("metadata")]
public class Metadata
{
[XmlElement("entry")]
public List<Entry> Entries { get; set; }
public Metadata()
{
Entries = new List<Entry>();
}
}
How it is made: entry contains dimensionInfo if it exists, and false if it does not. Try out the code sample in console:
Metadata metadata = new Metadata();
metadata.Entries.Add(new Entry()); //Adding empty entry
//Adding entry with info
metadata.Entries.Add(new Entry() { DimensionInfo = new DimensionInfo() });
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Metadata));
using (FileStream fileStream = new FileStream("info.txt", FileMode.Create))
{
xmlSerializer.Serialize(fileStream, metadata);
}
It gave me the following output:
<metadata>
<entry key="">false</entry>
<entry key="">
<dimensionInfo>
<enabled>false</enabled>
</dimensionInfo>
</entry>
</metadata>
However, this code is not perfect and there's a lot of things to improve, but now you have an idea of how it works
If you ever encounter XML files that you need to consume via .Net there is a tool XML Schema Definition Tool (Xsd.exe) that does the conversion automatically.
Its a command-line tool and to open it you use the Start > All
Programs > Visual Studio 2008/10/12/13 > Visual Studio Tools > Visual
Studio Command Prompt
The syntax to create a class from the XML file is described in the MSDN article. I'll give you an overview of the process to get you started.
Save the xml file to the temp dirtectory:
<metadata>
<entry key="">
<dimensionInfo>
<enabled>false</enabled>
</dimensionInfo>
</entry>
<entry key="">false</entry>
</metadata>
Fire up the Visual Studio Command Prompt
To find out all the options enter xsd /?
Now you need to convert the XML file to a XSD file, this is the command:
xsd "C:\temp\File.xml" /c /outputdir:c:\temp
This will create an XSD file in the temp directory.
Then to convert the XSD file to a Serializable C# class this is the command:
xsd "C:\temp\File.xsd" /c /outputdir:c:\temp
This is the resulting C# Serialized class file:
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)]
public partial class metadata {
private metadataEntry[] itemsField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("entry", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
public metadataEntry[] Items {
get {
return this.itemsField;
}
set {
this.itemsField = value;
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
public partial class metadataEntry {
private metadataEntryDimensionInfo[] dimensionInfoField;
private string keyField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("dimensionInfo", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
public metadataEntryDimensionInfo[] dimensionInfo {
get {
return this.dimensionInfoField;
}
set {
this.dimensionInfoField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string key {
get {
return this.keyField;
}
set {
this.keyField = value;
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
public partial class metadataEntryDimensionInfo {
private string enabledField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string enabled {
get {
return this.enabledField;
}
set {
this.enabledField = value;
}
}
}
Sounds like;
public class Metadata
{
private Dictionary<string, object> entry;
}
Where the dictionary has two item; "false" and another object;
public class dimensionInfo
{
public bool enabled = false;
}
However, you have 2 keys that are empty string. Are you sure the keys shouldn't have a value?
Actually you can create your own custom serialization for any class, so you don't have to stick to the schema when implementing your class.
All you have to do is to create a class which implements the IXmlSerializable interface
You can read more on the topic here:
Custom XML serialization
Otherwise you can use Attributes to control XML serialization.
Using [XmlAttribute] results in serialization as attribute.
Using [XmlText] results in serialization as inner text in the node. (Like the false string inside the other element in your example XML.)
Example:
public class Program
{
static void Main(string[] args)
{
Metadata meta = new Metadata();
meta.entry = new List<Entry>();
var dim = new dimensionInfo();
meta.entry.Add(
new Entry()
{
key = "",
O = dim
}
);
meta.entry.Add(
new Entry()
{
key = "",
text = "false",
O = null
}
);
XmlWriterSettings set = new XmlWriterSettings();
set.NamespaceHandling = NamespaceHandling.OmitDuplicates;
set.OmitXmlDeclaration = true;
set.DoNotEscapeUriAttributes = false;
set.Indent = true;
set.NewLineChars = "\n\r";
set.IndentChars = "\t";
XmlWriter writer = XmlWriter.Create(Console.Out, set);
XmlSerializer ser = new XmlSerializer(typeof(Metadata), "");
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
ser.Serialize(writer, meta, namespaces);
}
[XmlRoot("metadata")]
public class Metadata
{
[XmlElement]
public List<Entry> entry;
}
public class dimensionInfo
{
[XmlElement]
public bool enabled = false;
}
public class Entry
{
[XmlAttribute] // serialized as attribute
public string key = "";
[XmlText] // serialized as text node
public string text = "";
[XmlElement("dimensionInfo")] // serialized as an element
public dimensionInfo O = null;
}
}
This resulted the following XML:
<metadata>
<entry key=""><dimensionInfo><enabled>false</enabled></dimensionInfo></entry>
<entry key="">false</entry>
</metadata>
You can use XSD.exe to autoGenerate C# class in which you can serialize.
A straight forward answer:
Having your class to implement IXmlSerializable interface and using an XmlSerializer to defining the behavior for all the cases you desire.
private DimensionInfo _value;
public void WriteXml(XmlWriter writer)
{
var valueSerializer = new XmlSerializer(typeof (DimensionInfo));
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
writer.WriteStartElement("entry");
writer.WriteAttributeString("key", string.Empty, string.Empty);
// Here you define how you want your XML structure to look like
// E.g. write an empty XML node in case of a null value
if (_value != null)
{
valueSerializer.Serialize(writer, value, ns);
}
writer.WriteEndElement();
}
Would produce this in XML
<entry key="">
<dimensionInfo>
<enabled>true</enabled>
</dimensionInfo>
</entry>
Or in case of a null value
<entry key="" />
A more detailed example with a XmlSerializableDictionary:
I used an XmlSerializableDictionary approach to produce the XML you provided. Again, you can specify precisely how the produced XML should look like in the WriteXml method.
[XmlRoot("metadata")]
public class XmlSerializableDictionary<TValue> : Dictionary<string, TValue>, IXmlSerializable where TValue : class
{
private const string XmlKeyName = "key";
private const string XmlValueName = "entry";
public void WriteXml(XmlWriter writer)
{
var valueSerializer = new XmlSerializer(typeof (TValue));
var ns = new XmlSerializerNamespaces(); ns.Add("", "");
foreach (var key in Keys)
{
writer.WriteStartElement(XmlValueName);
writer.WriteAttributeString(XmlKeyName, string.Empty, key);
var value = this[key];
// Only serialize the value if value is not null, otherwise write the
// empty XML element.
if (value != null)
{
valueSerializer.Serialize(writer, value, ns);
}
writer.WriteEndElement();
}
}
public void ReadXml(XmlReader reader) { /* left out */ }
public XmlSchema GetSchema() { return null; }
}
The DimensionInfo type for reference
[XmlRoot("dimensionInfo")]
public class DimensionInfo
{
[XmlElement("enabled")]
public Boolean Enabled { get; set; }
}
The following code serializes the dictionary to XML
var xmlSerializableDictionary = new XmlSerializableDictionary<DimensionInfo>
{
{"justakey", new DimensionInfo {Enabled = true}},
{"anotherkey", null}
};
var xmlSerializer = new XmlSerializer(typeof (SerializableDictionary<DimensionInfo>));
xmlSerializer.Serialize(File.Open(#"D:\xmlSerializedDictionary.xml", FileMode.Create), serializableDictionary);
Produced XML file:
<?xml version="1.0"?>
<metadata>
<entry key="justakey">
<dimensionInfo>
<enabled>true</enabled>
</dimensionInfo>
</entry>
<entry key="anotherkey" />
</metadata>

XMLSerializer fails to deserialize a xml document

I am a newbie to C#. I have a java REST service which returns a xml response and I am trying to deserialize the xml document using C# XmlSerializer. A sample xml document response is pasted below.
<?xml version="1.0" encoding="UTF-8"
standalone="yes" ?> <ns2:Document
xmlns:ns2="http://hxps.honeywell.com/model/impl"
xmlns:ns3="http://hxps.honeywell.com/datatypes/impl"
type="PS">
<docId>SamplePSDocument1</docId>
<fields
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ns5="http://jaxb.dev.java.net/array"
xsi:type="ns5:anyTypeArray"> <item
xsi:type="ns3:scalarFieldImpl"
key="Name">
<value xmlns:xs="http://www.w3.org/2001/XMLSchema"
xsi:type="xs:string">HXPS A4</value>
</item> <item
xsi:type="ns3:scalarFieldImpl"
key="Creation Date">
<value xmlns:xs="http://www.w3.org/2001/XMLSchema"
xsi:type="xs:string">20 April
2007</value> </item> </fields>
<id>fb92f871-1f3d-4fa4-ba24-5ae3af0a493f</id>
<revision>1-c75f688e212fb5341ebdbd22a3867c14</revision>
- <version> <majorVersionNumber>1</majorVersionNumber>
<minorVerisonNumber>5</minorVerisonNumber>
</version> </ns2:document>
It works fine when I deserialize this xml document into Document object. My document class is pasted below
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://hxps.honeywell.com/model/impl", TypeName = "PSDocument")]
[System.Xml.Serialization.SoapTypeAttribute(Namespace = "http://hxps.honeywell.com/model/impl", TypeName = "PSDocument")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://hxps.honeywell.com/model/impl", ElementName = "Document")]
public partial class PSDocument
{
private Com.Honeywell.Hxps.Sdk.Model.DocumentType _type;
private string _description;
private string _displayName;
private string _docId;
private Com.Honeywell.Hxps.Sdk.Model.Impl.VersionImpl _version;
private object _fields;
private string _revision;
private string _id;
/// <summary>
/// (no documentation provided)
/// </summary>
[System.Xml.Serialization.XmlAttributeAttribute(AttributeName = "type")]
[System.Xml.Serialization.SoapAttributeAttribute(AttributeName = "type")]
public Com.Honeywell.Hxps.Sdk.Model.DocumentType Type
{
get
{
return this._type;
}
set
{
this._type = value;
}
}
/// <summary>
/// Property for the XML serializer indicating whether the "Type" property should be included in the output.
/// </summary>
[System.Xml.Serialization.XmlIgnoreAttribute]
[System.Xml.Serialization.SoapIgnoreAttribute]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public bool TypeSpecified
{
get
{
return this._type != Com.Honeywell.Hxps.Sdk.Model.DocumentType.NULL;
}
set
{
if (!value)
{
this._type = Com.Honeywell.Hxps.Sdk.Model.DocumentType.NULL;
}
}
}
/// <summary>
/// (no documentation provided)
/// </summary>
[System.Xml.Serialization.XmlElementAttribute(ElementName = "description", Namespace = "")]
[System.Xml.Serialization.SoapElementAttribute(ElementName = "description")]
public string Description
{
get
{
return this._description;
}
set
{
this._description = value;
}
}
/// <summary>
/// (no documentation provided)
/// </summary>
[System.Xml.Serialization.XmlElementAttribute(ElementName = "displayName", Namespace = "")]
[System.Xml.Serialization.SoapElementAttribute(ElementName = "displayName")]
public string DisplayName
{
get
{
return this._displayName;
}
set
{
this._displayName = value;
}
}
/// <summary>
/// (no documentation provided)
/// </summary>
[System.Xml.Serialization.XmlElementAttribute(ElementName = "docId", Namespace = "")]
[System.Xml.Serialization.SoapElementAttribute(ElementName = "docId")]
public string DocId
{
get
{
return this._docId;
}
set
{
this._docId = value;
}
}
/// <summary>
/// (no documentation provided)
/// </summary>
[System.Xml.Serialization.XmlElementAttribute(ElementName = "version", Namespace = "")]
[System.Xml.Serialization.SoapElementAttribute(ElementName = "version")]
public Com.Honeywell.Hxps.Sdk.Model.Impl.VersionImpl Version
{
get
{
return this._version;
}
set
{
this._version = value;
}
}
/// <summary>
/// (no documentation provided)
/// </summary>
[System.Xml.Serialization.XmlElementAttribute(ElementName = "fields", Namespace = "")]
[System.Xml.Serialization.SoapElementAttribute(ElementName = "fields")]
public object Fields
{
get
{
return this._fields;
}
set
{
this._fields = value;
}
}
/// <summary>
/// (no documentation provided)
/// </summary>
[System.Xml.Serialization.XmlElementAttribute(ElementName = "revision", Namespace = "")]
[System.Xml.Serialization.SoapElementAttribute(ElementName = "revision")]
public string Revision
{
get
{
return this._revision;
}
set
{
this._revision = value;
}
}
/// <summary>
/// (no documentation provided)
/// </summary>
[System.Xml.Serialization.XmlElementAttribute(ElementName = "id", Namespace = "")]
[System.Xml.Serialization.SoapElementAttribute(ElementName = "id")]
public string Id
{
get
{
return this._id;
}
set
{
this._id = value;
}
}
}
}
In my main program, I get an array of xmlNodes when I try
Array fields = (Array)doc.Fields;
In the server side java REST service implementation, fields is actually a arraylist which will contain instances of three implementations of an interface. (List may contain ScalarFieldImpl or ArrayFieldImpl which are custom business objects).
I want to deserialize this xml fields into ScalarFieldImpl or ArrayFieldImpl using XmlSerializer. I want to know whether it is possible? If so, how do I do that?
As far as I know this isn't possible with the ootb xml (de-)serializer, I think you'll have to write your own XmlSerializer. Or try to use LINQ to XML.
I got this working. I did few changes in my PSDocument class.
private object _fields; -> private ArrayList _fields;
Also I changed get/set method and added new metadata
[System.Xml.Serialization.XmlArray(ElementName = "fields", Namespace = "")]
[System.Xml.Serialization.XmlArrayItem(ElementName="item")]
public ArrayList Fields
{
get
{
return this._fields;
}
set
{
this._fields = value;
}
}
Previously I had the following lines in my PSDocument class
[System.Xml.Serialization.XmlElementAttribute(ElementName = "fields", Namespace = "")]
[System.Xml.Serialization.SoapElementAttribute(ElementName = "fields")]
public object Fields
{
get
{
return this._fields;
}
set
{
this._fields = value;
}
}
So while deserializing fields in PSDocument, I get an arraylist with items as elements.
Hope this helps someone.

Categories

Resources