I have a webservice that is working without a problem (asmx running in iis). The only problem is that the client is really strict with the XML output. The current format is the following:
<ArrayOfVenda xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://213.63.189.121/webservicenos">
<venda>
<id>x</id>
<contact_moment>x</contact_moment>
</venda>
<venda>
<id>y</id>
<contact_moment>y</contact_moment>
</venda>
</ArrayOfVenda>
And it should be:
<ArrayOfVenda xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://213.63.189.121/webservicenos">
<root>
<venda>
<id>x</id>
<contact_moment>x</contact_moment>
</venda>
<venda>
<id>y</id>
<contact_moment>y</contact_moment>
</venda>
</root>
</ArrayOfVenda>
So the only thing is adding a XMLElement with the name root that contains the list venda. I'm having trouble in adding this element tho i really don't know how to go about it in my code. Here it is:
[WebMethod]
[return: System.Xml.Serialization.XmlElementAttribute("venda")]
public List<venda> getListaVendas(string dt_min, string dt_max)
{
List<venda> objVendaList = new List<venda>();
using (SqlConnection con = new SqlConnection(#"Data Source=server;Initial Catalog=db;User ID=user;password=password"))
{
using (SqlCommand cmd = new SqlCommand("SELECT * FROM dbo.vcnosadesoes_getlistavendas where contact_moment >='" + dt_min + "' AND contact_moment <DATEADD(dd, 1, '" + dt_max + "')", con))
{
con.Open();
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
var objVenda = new venda();
objVenda.id = dr["id"].ToString();
objVenda.contact_moment = dr["contact_moment"].ToString();
objVenda.nome = dr["nome"].ToString();
objVenda.pacote = dr["pacote"].ToString();
objVenda.telefone = dr["telefone"].ToString();
objVenda.codigo_wc = dr["codigo_wc"].ToString();
objVendaList.Add(objVenda);
}
dr.Close();
}
}
return objVendaList;
}
Any ideas what is the best method to add this element?
PS: I KNOW. I have to change the SQL Query because of SQL Injections i will get to that before putting it live don't worry. Also this Line:
[return: System.Xml.Serialization.XmlElementAttribute("venda")]
may be doing nothing i just put it up for some tests and never comment it out.
UPDATE : So the script from the client is still returning the error. After hours looking at the debugger i discovered what it need is this output:
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://213.63.189.121/webservicenos">
<venda>
<id>x</id>
<contact_moment>x</contact_moment>
</venda>
<venda>
<id>y</id>
<contact_moment>y</contact_moment>
</venda>
</root>
Since the XML has an extra level of testing (the <root> element between <ArrayOfVenda> and <venda>) the c# types you return have to have a similar structure to be successfully serialized in that form. Thus, introduce a new root type ArrayOfVenda:
[XmlRoot(ElementName = "ArrayOfVenda", Namespace = "http://213.63.189.121/webservicenos")]
public class ArrayOfVenda
{
[XmlArray("root")]
[XmlArrayItem("venda")]
public List<venda> VendaList { get; set; }
}
And return that from your getListaVendas method:
public ArrayOfVenda getListaVendas(string dt_min, string dt_max)
{
List<venda> objVendaList = new List<venda>();
// Fill in objVendaList as you do currently
return new ArrayOfVenda { VendaList = objVendaList };
}
Update
Given your new requirements for your XML output, you can define your ArrayOfVenda class as follows:
[XmlRoot(ElementName = "root", Namespace = "http://213.63.189.121/webservicenos")]
public class ArrayOfVenda
{
[XmlElement("venda")]
public List<venda> VendaList { get; set; }
}
[XmlRoot(...)] defines the name and namespace of ArrayOfVenda when it is the root XML element. [XmlElement("venda")] indicates that the VendaList will be formatted without an outer container element, and that each item is to be named <venda>.
Related
I am working with an XML api, which returns the following:
<inventory>
<product inventoryId="1722474" externalReference="SM" site="global" total="0" allocated="0" available="0" frozen="0" onOrder="0" lastStockChangeId="505401" lastLineRequirementChangeId="0"/>
<product inventoryId="1722476" externalReference="PM" site="global" total="0" allocated="0" available="0" frozen="0" onOrder="0" lastStockChangeId="243256" lastLineRequirementChangeId="0"/>
.... 1000s of nodes ....
</inventory>
So, from this returned xml nodes, I am only interested in the following fields/attributes externalReference and available.
Therefore; I created the following class to describe the xml content I am going to deserialize/parse:
[XmlRoot(ElementName = "product")]
public class StockLevelProduct
{
[XmlAttribute(AttributeName = "externalReference")]
public string ExternalReference { get; set; }
[XmlAttribute(AttributeName = "available")]
public string Available { get; set; }
}
[XmlRoot(ElementName = "inventory")]
public class StockLevelResult
{
[XmlElement(ElementName = "product")]
public List<StockLevelProduct> Product { get; set; }
}
Then I put it all together like this:
// Init
StockLevelResult stockLevelResult;
// Anticipate errors
try
{
// Generate request url
string requestUrl = string.Format("{0}/remotewarehouse/inventory.xml?channel={1}",
apiUrl,
apiChannel);
// Call api
string apiResultXmlString = ApiGet(requestUrl);
// Fix api result xml string
if (!apiResultXmlString.Contains("<?xml"))
apiResultXmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + apiResultXmlString;
// Deserialize xml string to object
stockLevelResult = XmlParser.Parse<StockLevelResult>(apiResultXmlString);
}
catch (Exception ex)
{
Console.WriteLine("Failed to download stock levels - " + ex.Message);
}
Note* the returned xml string from the API server does not contain <?xml version="1.0" encoding="UTF-8"?> so I manually add it. Note sure if XmlParser.Parse requires it.
When this code executes; I get the following exception being thrown:
There is an error in XML document (1871, 60)
Any ideas why this isn't working? Is it an issue with the returned XML string? or the way I am trying to parse/deserialize it?
Try this
XmlSerializer xs = new XmlSerializer(typeof(StockLevelResult));
StringReader sReader = new StringReader(apiResultXmlString);
XmlTextReader reader = new XmlTextReader(sReader);
stockLevelResult = (StockLevelResult)xs.Deserialize(reader);
The exception was caused by bad data in the api XML response:
<product inventoryId="1726460" externalReference="V02002B&R"
site="global" total="0" allocated="0" available="0" frozen="0"
onOrder="0" lastStockChangeId="76231"
lastLineRequirementChangeId="0"/>
i.e. & in this attribute externalReference="V02002B&R"
I fixed it thanks to this answer like this:
// Call api
string apiResultXmlString = ApiGet(requestUrl);
// Fix api result xml string
if (!apiResultXmlString.Contains("<?xml"))
apiResultXmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + apiResultXmlString;
// Fix bad response data
string pattern = "(?<start>>)(?<content>.+?(?<!>))(?<end><)|(?<start>\")(?<content>.+?)(?<end>\")";
apiResultXmlString = System.Text.RegularExpressions.Regex.Replace(apiResultXmlString, pattern, m =>
m.Groups["start"].Value +
System.Web.HttpUtility.HtmlEncode(System.Web.HttpUtility.HtmlDecode(m.Groups["content"].Value)) +
m.Groups["end"].Value);
I'm using visual studio for windows phone and my code for the XML reader does not work when there is attributes in the parent of the XML data.
My C# code
namespace youtube_xml
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
SupportedOrientations = SupportedPageOrientation.PortraitOrLandscape;
}
private void listBox1_Loaded(object sender, RoutedEventArgs e)
{
var element = XElement.Load("Authors.xml");
var authors =
from var in element.Descendants("feed")
select new Authors
{
AuthorName = var.Attribute("scheme").Value,
};
listBoxAuthors.DataContext = authors;
}
public ImageSource GetImage(string path)
{
return new BitmapImage(new Uri(path, UriKind.Relative));
}
}
}
The Working XML data
<?xml version='1.0' encoding='UTF-8'?>
<feed>
<category scheme='http://schemas.google.com/g/2005#kind'/>
</feed>
NOT working data (note: the attribute "xmlns" in the root element "feed")
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' >
<category scheme='http://schemas.google.com/g/2005#kind'/>
</feed>
Welcome to the world of XML namespaces! The problem isn't the fact that "there's an attribute" - it's the fact that it's causing everything below it to be in a namespace. You can no longer say .Attribute("scheme") because that only looks for things in the empty namespace. Namespaces are used via a contraption based on operator overloading:
XNamespace atom = "http://www.w3.org/2005/Atom'";
// And now you can say:
.Descendants(atom + "feed")
.Attribute(atom + "scheme")
Et cetera. The ability to assign a string into an XNamespace variable is thanks to an implicit conversion operator. The + here actually constructs an XName (which, by the way, also has an implicit conversion from string - that's why you plain .Elements("feed") works even though the parameter type is not string)
Handy tip: You can cast an attribute into certain types instead of using .Value, for instance (string)foo.Attribute(atom + "scheme"). It also works with a bunch of other types, for instance int.
I made a request to a third party API and it gives me the following response in XML.
<?xml version="1.0" ?>
<abc>
<xyz>
<code>-112</code>
<message>No such device</message>
</xyz>
</abc>
I read this using this code.
XmlDocument doc = new XmlDocument();
doc.Load("*** url ***");
XmlNode node = doc.SelectSingleNode("/abc/xyz");
string code = node.SelectSingleNode("code").InnerText;
string msg = node.SelectSingleNode("message").InnerText;
Response.Write("Code: " + code);
Response.Write("Message: "+ msg);
But I get an error on this line:
string code = node.SelectSingleNode("code").InnerText;
Error is:
Object reference not set to an instance of an object.
I changed the first line of your XML file into:
<?xml version="1.0"?>
to make it valid XML. With this change, your code works for me. Without the change, the parser throws an exception.
You can use LINQ to XML (if confortable):
XDocument doc = XDocument.Load(url);
var selectors = (from elements in doc.Elements("abc").Elements("xyz")
select elements).FirstOrDefault();
string code = selectors.Element("code").Value;
string msg = selectors.Element("message").Value;
As you've given it, there doesn't seem to be anything wrong with your code Edit : Your declaration is wrong, as svinja pointed out, and your xml won't even load into the XmlDocument.
However, I'm guessing that your xml is more complicated, and there is at least one namespace involved, which would cause the select to fail.
It isn't pretty, but what you can do is use namespace agnostic xpath to locate your nodes to avoid using a XmlNamespaceManager:
XmlDocument doc = new XmlDocument();
doc.Load("*** url ***");
XmlNode node = doc.SelectSingleNode("/*[local-name()='abc']/*[local-name()='xyz']");
string code = node.SelectSingleNode("*[local-name()='code']").InnerText;
string msg = node.SelectSingleNode("*[local-name()='message']").InnerText;
Response.Write("Code: " + code);
Response.Write("Message: "+ msg);
Edit - Elaboration
Your code works fine if you correct the declaration to <?xml version="1.0"?>
However, if you introduce namespaces into the mix, your code will fail unless you use namespace managers appropriately.
My agnostic xpath above will also parse an xml document like so:
<?xml version="1.0"?>
<abc xmlns="foo">
<xyz xmlns="bar">
<code xmlns="bas">-112</code>
<message xmlns="xyz">No such device</message>
</xyz>
</abc>
<?xml version="1.0">
<abc>
<xyz>
<code>-112</code>
<message> No such device </message>
</xyz>
</abc>
try to set a list:
XmlNodeList nodeList = root.SelectNodes("/abc/xyz");
then read all the nodes and get their text:
foreach(XmlNode node in nodeList)
{
if(node.Name == "code")
{
string code = node.InnerText;
}
else
if(node.Name == "message")
{
string msg = node.InnerText;
}
}
[XmlRoot("abc")]
public class Entity
{
[XmlElement("xyz")]
public SubEntity SubEntity { get; set; }
}
public class SubEntity
{
[XmlElement("code")]
public string Code { get; set; }
[XmlElement("message")]
public string Message { get; set; }
}
And use standart xmlserializer
var xmlSerializer = new XmlSerializer(typeof(Entity));
var result = xmlSerializer.Deserialize(new XmlTextReader("*** url ***"));
Response.Write("Code: " + result.SubEntity.Code);
Response.Write("Message: "+ result.SubEntity.Message);
Thank you very much for reading my question.
the bottom is the sample of my xml file.please refer that.
i did some xml files before, but by "CMarkXml". "IntoElement, OutofElement", is very clear.
but when C#...i was lost..
1: how to read & write my xml file without using the tag name. i see some articles about operation on xml file by c#, but all assumed that known the tag name.
2: if without tag name, it is very difficult or not recommend. then how to read & write my xml file by XmlDocument? (sorry, but no Ling please, i am very faint with that...).
3: my idear is, for the xml file, get out some section, we still could parse the section by xmldocument.
4: for the write/modify the xml file, of course, should contain delete some section, delete some "leaf", change the attributes...
Thank you very much for reading the long question, and any help i will very appreciate. If you have a good sample code but not continent paste them here, could you send it to "erlvde#gmail.com"?
<root>
<a>i belong to a</a>
<b>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
<bb>
....(other <bb>)
</b>
</root>
Read your xml into XmlDocument:
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml("XML HERE");
Access child nodes:
xmlDocument.ChildNodes[1]
But it's also true that it's very error prone
You can also check if you have child nodes at all:
xmlDocument.HasChildNodes
And get number of child nodes:
xmlDocument.ChildNodes.Count
It looks to me like your elements names contain identifiers. If that is the case, and you have control over the XML schema, I would highly recommend changing your XML to contain elements and/or attributes indicating your identifiers and then use the built in XmlSerializer class for serializing to and from XML. It has many modifiers available, such as XmlElement and XmlAttribute among many others, for formatting the output.
Here is a tutorial to get you started.
If possible, change your XML to something like following which would make it far simpler to manipulate...again if changing the schema is a possibility.
<root>
<a>i belong to a</a>
<b>
<bb id="1">
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
<bb id="2">
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
<bb>
</b>
</root>
Edit this edit reflects the changes you made to your XML
Here is a simple console application which will serialize an object to an XML file and then rehydrate it.
Expected XML
<?xml version="1.0" encoding="utf-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<a>i belong to a</a>
<b>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
</b>
</root>
Simple Console Application Demonstration
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var items = new root
{
a = "i belong to a",
b = new List<bb>
{
new bb
{
bbClassProperty = new List<int>
{
1,
2,
3,
4,
5
}
},
new bb
{
bbClassProperty= new List<int>
{
1,
2,
3,
4,
5
}
}
}
};
XmlSerializer serializer = new XmlSerializer(typeof(root));
using (var textWriter = new StreamWriter(#"C:\root.xml"))
{
serializer.Serialize(textWriter, items);
textWriter.Close();
}
using (var stream = new StreamReader(#"C:\root.xml"))
{
var yourObject = serializer.Deserialize(stream);
}
Console.Read();
}
}
#region [Classes]
public class root
{
public string a { get; set; }
public List<bb> b { get; set; }
}
public class bb
{
[XmlElement("bb")]
public List<int> bbClassProperty { get; set; }
}
#endregion
}
Look into the ChildNodes (and similar) properties and methods on your XmlElement object. These will let you iterate over the children of a node and you can then ask that node for its name.
If you have a XmlNode object, you can use XMLNode.FirstChild to get the child, if it has any. You can also use XMLNode.NextSibling to get the next Node of the same parent node.
Why can't you use the names of the nodes? It's the easiest and most common way. Especially if you use XPath or similar.
XPath is also the answer to your second question.
U can use the class XML reader, a simple example is given here.
using System;
using System.Xml;
class Program
{
static void Main()
{
// Create an XML reader for this file.
using (XmlReader reader = XmlReader.Create("perls.xml"))
{
while (reader.Read())
{
// Only detect start elements.
if (reader.IsStartElement())
{
// Get element name and switch on it.
switch (reader.Name)
{
case "perls":
// Detect this element.
Console.WriteLine("Start <perls> element.");
break;
case "article":
// Detect this article element.
Console.WriteLine("Start <article> element.");
// Search for the attribute name on this current node.
string attribute = reader["name"];
if (attribute != null)
{
Console.WriteLine(" Has attribute name: " + attribute);
}
// Next read will contain text.
if (reader.Read())
{
Console.WriteLine(" Text node: " + reader.Value.Trim());
}
break;
}
}
}
}
}
}
The input file text is:
<?xml version="1.0" encoding="utf-8" ?>
<perls>
<article name="backgroundworker">
Example text.
</article>
<article name="threadpool">
More text.
</article>
<article></article>
<article>Final text.</article>
</perls>
Output
Start element.
Start element.
Has attribute name: backgroundworker
Text node: Example text.
Start element.
Has attribute name: threadpool
Text node: More text.
Start element.
Text node:
Start element.
Text node: Final text.enter code here
You can use the following code to if the file does not contain the headers, in the example above.
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
reader = XmlReader.Create(filePath, settings)
Would something like this help?
void Iterate(XmlNode parent) {
//do something with
//parent.Name
//parent.Value
//parent.Attributes
foreach(XmlNode child in parent.ChildNodes) {
Iterate(child);
}
}
XmlDocument document = new XmlDocument();
document.Load(filename);
XmlNode parent = document.DocumentElement;
Iterate(parent);
You could also store it like that (sorry for any syntactical error, didn't run it)
public class Document {
public Element DocumentElement { set; get; }
private void Load(string fileName) {
XmlDocument document = new XmlDocument();
document.Load(fileName);
DocumentElement = new Element(this, null);
DocumentElement.Load(document.DocumentElement);
}
}
public class Element {
public string Name { set; get; }
public string Value { set; get; }
//other attributes
private Document document = null;
private Element parent = null;
public Element Parent { get { return parent; } }
public List<Element> Children { set; get; }
private int order = 0;
public Element(Document document, Element parent) {
Name = "";
Value = "";
Children = new List<LayoutElement>();
this.document = document;
this.parent = parent;
order = parent != null ? parent.Children.Count + 1 : 1;
}
private Element GetSibling(bool left) {
if(parent == null) return null;
int add = left ? -1 : +1;
Element sibling = parent.Children.Find(child => child.order == order + add);
return sibling;
}
public Element GetLeftSibling() {
return GetSibling(true);
}
public Element GetRightSibling() {
return GetSibling(false);
}
public void Load(XmlNode node) {
Name = node.Name;
Value = node.Value;
//other attributes
foreach(XmlNode nodeChild in node.Children) {
Element child = new Element(document, this);
child.Load(nodeChild);
Children.Add(child);
}
}
}
Document document = new Document();
document.Load(fileName);
For changing/deleting right now you could iterate the tree and find elements by name, but since name is not unique, you would affect many elements at once. You could add an unique id in every tag like
<bb id="bb1"/>
Then read it in Load function like
id = ((XmlElement)node).GetAttribute("id");
and use this id to iterate through the tree. Sorry I don't have time right now to provide something more detailed.
i have tried several methods of trying to extract values from an XML file but none of them seem to work. I am using C#. The XML Is as follows
<?xml version="1.0" encoding="utf-8"?>
<HostedService xmlns="http://schemas.microsoft.com/windowsazure">
<Url>hosted-service-url</Url>
<ServiceName>hosted-service-name</ServiceName>
<HostedServiceProperties>
<Description>description</Description>
<Location>location</Location>
<AffinityGroup>affinity-group</AffinityGroup>
<Label>label</Label>
</HostedServiceProperties>
</HostedService>
I would like to retrieve
hosted-service-url,
hosted-service-name,
description,
location,
affinity-group and
label
What would be the best of way of retrieving these values?
Edit :
Thanks L.B that method works perfectly. However i have just been told i will have to use the larger XML that is below.
<?xml version="1.0" encoding="utf-8"?>
<HostedService xmlns="http://schemas.microsoft.com/windowsazure">
<Url>hosted-service-url</Url>
<ServiceName>hosted-service-name</ServiceName>
<HostedServiceProperties>
<Description>description</Description>
<Location>location</Location>
<AffinityGroup>affinity-group</AffinityGroup>
<Label>base-64-encoded-name-of-the-service</Label>
</HostedServiceProperties>
<Deployments>
<Deployment>
<Name>deployment-name</Name>
<DeploymentSlot>deployment-slot</DeploymentSlot>
<PrivateID>deployment-id</PrivateID>
<Status>deployment-status</Status>
<Label>base64-encoded-deployment-label</Label>
<Url>deployment-url</Url>
<Configuration>base-64-encoded-configuration-file</Configuration>
<RoleInstanceList>
<RoleInstance>
<RoleName>role-name</RoleName>
<InstanceName>role-instance-name</InstanceName>
<InstanceStatus>instance-status</InstanceStatus>
</RoleInstance>
</RoleInstanceList>
<UpgradeDomainCount>upgrade-domain-count</UpgradeDomainCount>
<RoleList>
<Role>
<RoleName>role-name</RoleName>
<OsVersion>operating-system-version</OsVersion>
</Role>
</RoleList>
<SdkVersion>sdk-version-used-to-create-package</SdkVersion>
<InputEndpointList>
<InputEndpoint>
<RoleName>role-name</RoleName>
<Vip>virtual-ip-address</Vip>
<Port>port-number</Port>
</InputEndpoint>
…
</InputEndpointList>
<Locked>deployment-write-allowed-status</Locked>
<RollbackAllowed>rollback-operation-allowed</RollbackAllowed>
</Deployment>
</Deployments>
</HostedService>
My final question is, there is several repeated tags, such as ,
how can i differentiate between them?
you can use Xml to Linq to parse your xml string. For ex,
var xElem = XElement.Load(new StringReader(xml));
var ns = XNamespace.Get("http://schemas.microsoft.com/windowsazure");
var obj = new
{
ServiceName = xElem.Descendants(ns + "ServiceName").First().Value,
Description = xElem.Descendants(ns + "Description").First().Value,
};
or you can use XmlSerializer
XmlSerializer xs = new XmlSerializer(typeof(HostedService), "http://schemas.microsoft.com/windowsazure");
var obj2 = (HostedService)xs.Deserialize(new StringReader(xml));
public class HostedService
{
public string Url;
public string ServiceName;
public HostedServiceProperties HostedServiceProperties;
}
public class HostedServiceProperties
{
public string Description;
public string Location;
public string AffinityGroup;
public string Label;
}
Maybe you can try samples from XmlDocument ( http://msdn.microsoft.com/en-us/library/d271ytdx.aspx) and and LINQ to XML -( http://msdn.microsoft.com/en-us/library/bb669152.aspx) first and than apply it to your case.