newbie here as long as web API is concerned.
web Api:
[HttpGet]
public IHttpActionResult Test()
{
var doc = new XmlDocument();
XmlElement tournament = (XmlElement)doc.AppendChild(doc.CreateElement("Tournament"));
XmlElement match = (XmlElement)tournament.AppendChild(doc.CreateElement("Match"));
match.SetAttribute("ID", "SomeMatch");
return Ok(doc.InnerXml);
}
Result:
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/"><Tournament><Match ID="SomeMatch" /></Tournament></string>
Two problems:
why it is wrapped in this string element when my XML does not have it?
why < is converted to "& lt;" and > to "& gt;"
and how to get back just
<Tournament>
<Match ID="SomeMatch" /></Tournament>
Since you are returning a string from the action, ASP.Net Web API is creating the equivalent XML to represent a string.
Now, if you were to ensure that API uses XML serialization, client can add accept header to the request. Alternatively, you can specify the formatter either the way you have or in the action itself by returning Content using following constructor:
return Content (HttpStatusCodeHere, doc.DocumentElement, Configuration.Formatters.XmlFormatter)
Note that I do not have access to Visual Studio so class/property names might not be accurate.
You have to returns doc.DocumentElement instead of doc.InnerXml.
Because doc.InnerXml gives you xml in string format that's why your xml is shown in <string> format
[HttpGet]
public IHttpActionResult Test()
{
var doc = new XmlDocument();
XmlElement tournament = (XmlElement)doc.AppendChild(doc.CreateElement("Tournament"));
XmlElement match = (XmlElement)tournament.AppendChild(doc.CreateElement("Match"));
match.SetAttribute("ID", "SomeMatch");
return Ok(doc.DocumentElement);
}
All your api(s) returns output is in xml format because your HttpConfiguration set in XmlFormatter by default.
Related
I have an XML document wrapped in a SOAP header but I can't select a single node. I think I am just misunderstanding how to repsresent the tree when nodes are prepended with text. e.g. wsu:Timestamp, wsu:Created
Here is the top part of my XML:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns:itk="urn:nhs-itk:ns:201005">
<soap:Header>
<wsa:MessageID>39c6f52b-1be0-42a9-a219-5d6ececd1695</wsa:MessageID>
<wsa:Action>urn:nhs-itk:services:201005:SendCDADocument-v2-0</wsa:Action>
<wsa:To>http://127.0.0.1:4000/syncsoap</wsa:To>
<wsa:From>
<wsa:Address>http://localhost</wsa:Address>
</wsa:From>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="D6CD5232-14CF-11DF-9423-1F9A910D4703">
<wsu:Created>2015-01-30T19:40:00</wsu:Created>
<wsu:Expires>2015-01-30T19:50:00</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken>
<wsse:Username>TKS Server test</wsse:Username>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
Here is my code, which throws a null pointer exception on the Select Single Node because it doesn't return a match.
private static XmlDocument CreateSoapEnvelope()
{
XmlDocument soapEnvelop = new XmlDocument();
DateTime myCreatedDate = DateTime.UtcNow;
soapEnvelop.Load(#“c:\myFile.xml");
soapEnvelop.SelectSingleNode("/soap/Envelope/Header/wsse/Security/wsu/Timestamp/Created").InnerText = myCreatedDate.ToString("yyyy-MM-ddTHH:mm:ssZ");
return soapEnvelop;
}
I triple checked for case sensitivity and I don't see a mistake there. And when I put a breakpoint in I can see the XML is loaded into the soapEnvelop object fine.
I implemented the suggestion of using a Namespace Manager and I could select several nodes fine. However, I have an issue with matching the Id value in wsu:Timestamp. I tried the following but all couldn't match:
soapEnvelop.SelectSingleNode("//soap:Envelope/soap:Header/wsse:Security/wsu:Timestamp/Id", ns).InnerText = Guid.NewGuid().ToString();
soapEnvelop.SelectSingleNode("//soap:Envelope/soap:Header/wsse:Security/wsu:Timestamp/wsu:Id", ns).InnerText = Guid.NewGuid().ToString();
soapEnvelop.SelectSingleNode("//soap:Envelope/soap:Header/wsse:Security/wsu:Timestamp", ns).Attributes["Id"].Value = Guid.NewGuid().ToString();
I figured out my last problem. If you have an attribute that is empty then the Value() call must "fail". I had modified my xml doc I was working with when I added in the code suggested below and took out the existing ID and replaced it with "", which is what I was doing with Created and Expires. The issue must be you can have an InnerText for a node that is an empty string but you can't have that for an attribute. Maybe that is considered invalid XML so .Net says "no go dude".
What is odd is that when I changed my code to do this call before my initial setting of Id.
XmlNode myNode = soapEnvelop.SelectSingleNode("//soap:Envelope/soap:Header/wsse:Security/wsu:Timestamp", ns);
myNode.Attributes["wsu:Id"].Value = Guid.NewGuid().ToString();
It set the value fine even if Id was "" in my xml file. That is how I stumbled onto my solution because I left my original call still in the code and it didn't throw an exception so it got me thinking the only difference is Id has a value.
Thanks,
Dan
This is a namespace issue. The nodes that you are selecting don't all belong to the same namespace so effectively does not have access to them. You need to use an XmlNameSpaceManager to store your namespaces and specify which namespace each element belongs to.
Such as:
private static XmlDocument CreateSoapEnvelope()
{
XmlDocument soapEnvelop = new XmlDocument();
DateTime myCreatedDate = DateTime.UtcNow;
soapEnvelop.Load(#"c:\myFile.xml");
XmlNamespaceManager ns = new XmlNamespaceManager(soapEnvelop.NameTable);
ns.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
ns.AddNamespace("wsa", "http://www.w3.org/2005/08/addressing");
ns.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
ns.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
soapEnvelop.SelectSingleNode("//soap:Envelope/soap:Header/wsse:Security/wsu:Timestamp/wsu:Created", ns).InnerText = myCreatedDate.ToString("yyyy-MM-ddTHH:mm:ssZ");
}
There are more elegant ways of performing this task but this at least will allow you to proceed.
I'm patching together a C# Webservice which reads predefined SOAP responses from XML files. We're using this to mock a productive web service. The XML files contain the nodes directly child to ''. However, my current code adds a node to '', thus resulting in a wrong format.
What I got:
<soap:Body>
<Security_AuthenticateResponse xmlns="http://xml.tempuri.com/">
<Security_AuthenticateReply xmlns="http://xml.tempuri.com/VLSSLR_06_1_1A">
<processStatus>
...
</processStatus>
</Security_AuthenticateReply>
</Security_AuthenticateReply>
</soap:Body>
What we need:
<soap:Body>
<Security_AuthenticateReply xmlns="http://xml.tempuri.com/VLSSLR_06_1_1A">
<processStatus>
...
</processStatus>
</Security_AuthenticateReply>
</soap:Body>
My code:
[SoapDocumentMethod(Action = "http://webservices.amadeus.com/1ASIWABOAAI/VLSSLQ_06_1_1A",
RequestNamespace = "http://xml.amadeus.com/VLSSLQ_06_1_1A",
RequestElementName = "Security_Authenticate",
ResponseNamespace = "http://xml.amadeus.com/VLSSLR_06_1_1A",
ResponseElementName = "Security_AuthenticateReply")]
[WebMethod]
[return: XmlAnyElement]
[SoapHeader("sessionId", Direction = SoapHeaderDirection.Out)]
public XmlDocument Security_Authenticate()
{
.. some file magic here ...
XmlDocument rp = getFile("Security_AuthenticateReply.xml");
return rp;
}
I'm hoping for some directive which tells C# to drop one of the nodes. As an alternative, I guess I'll have to extract all child nodes and return those.
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);
I'm getting the exception that NodeType "None" is not supported when trying to run the following code.
public int ObjectContentI(string XmlPath)
{
XmlNodeReader xnr = new XmlNodeReader(this.xmlr.SelectSingleNode(XmlPath));
return xnr.ReadElementContentAsInt();
}
this.xmlr is a XmlDocument with a document successfully loaded in it. XmlPath contains a valid XPath url.
How do i set the NodeType (xnr.NodeType is readonly) or am I doing something else wrong?
Part of my XML:
<?xml version="1.0" encoding="utf-8" ?>
<ship weapons="0">
<cost>
<metal>250</metal>
<crystal>100</crystal>
</cost>
<health>
<shields>750</shields>
<sregene>10</sregene>
<hitpoints>1000</hitpoints>
<oxygen cps="2">25000</oxygen>
</health>
My XPath: "/ship/health/shields/text()"
Well, your approach is correct but not completely.
Let's suppose
XmlNode n = myXMLDoc.SelectSingleNode("/ship/health/shields/");
n.InnerXML OR n.InnerText should give you what you need.
Though conqenator provided you with code that fixed your problem following is reasoning why it didn't work in the first place:
If you don't call the Read method on a XmlNodeReader or any of the classes that derive from XmlReader, you will always get a XmlNodeType.None NodeType, which is the reason for the error. To fix the code you provided and to get an int back this is what the code needs to look like:
public int ObjectContentI(string XmlPath)
{
int result;
using(XmlNodeReader xnr = new XmlNodeReader(this.xmlr.SelectSingleNode(XmlPath))){
while(xnr.Read()){
result = xnr.ReadElementContentAsInt();
}
}
return result;
}
Note that the XPath to get this wroking needs to change to /ship/health/shields since ReadElementContentAsInt() returns the content of the element and won't work on a Text Node, which what you get when using /ship/health/shields/text().
Notice that I have also wrapped the XmlNodeReader in a using block, which will dispose of the XmlNodeReader once you are done with it to free up resources.
I have wsdl from third party server. Ran svcutil and ended up wih a set of
XmlNode AMethod(object Request);
methods. There is a separate 100 page pdf describing response/request objects for each method
My thought was wrap web methods and use XmlSerializer to return strongly typed objects. Returned xml looks like this (i removed soap headers):
<Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="ResponseExt"
xmlns="http://www.thirdparty.com/lr/">
<Code>0</Code>
<Message>SUCCESS</Message>
<SessionId>session_token</SessionId>
</Response>
Looked simple. Created a class(from document/wire captures):
[XmlRoot("Response")]
//EDIT added XmlType
[XmlType("ResponseExt", Namespace = "http://www.thirdparty.com/lr/")]
public class MyClass {
public string Code {get; set;}
public string Message {get; set;}
public string SessionId {get; set;}
}
Processing time:
//XmlNode node = xml from above
XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
XmlNodeReader reader = new XmlNodeReader(node);
Myclass myclass = serializer.Deserialize(reader) as MyClass
Last line is where it blows up with inner exception message: The specified type was not recognized: name='ResponseExt', namespace='http://www.thirdparty.com/lr/', at <Response xmlns=''>.
I can't figure out how to make Serializer happy and what exactly these two mean
xsi:type="ResponseExt"
xmlns="http://www.thirdparty.com/lr/
As always any advice and pointer are appreciated
EDIT: Accepted answer below.
I was still getting exception, until i found this, hopefully it'll save someone some time.
I started to work backwards. Captured xml on the wire. Deserialized to my created classes with correct attributes: worked like a charm. Tried again from webservice - exception. For some reason XmlSerializer doesn't recognize ResponseExt.
XmlSerializer serializer = new XmlSerializer(typeof(Response));
XmlNode node = (XmlNode)results[0];
XmlDocument doc = new XmlDocument();
doc.LoadXml(node.OuterXml); //reload node
XmlNodeReader reader = new XmlNodeReader(doc.FirstChild); //there is only one node
Response rsp = serializer.Deserialize(reader) as Response; //works
EDIT: underlying issue wsdl file was not complete. After spending 2 days on this and finding this (ugly) workaround, third-party vendor provided complete WSDL with all types that deserialize without errors.
Why are you manually deserializing XML, when you have WSDL ?
If you have WSDL, use the svcutil.exe tool, or the wsdl.exe tool, to generate proxy classes and DTOs for the XML messages being sent and received on the wire.
The point of a web services toolkit, or "stack" is to provide this for you, so that you don't have to author classes and XML serialization code by hand.
Did you try this? Did you try to run the WSDL through one of those tools? Or did you try to "Add web reference" in Visual Studio?
After updating the question, I suggest that you modify the WSDL, rather than write custom code. You can produce a custom WSDL for the service, which will correctly generate the proxy classes you want. If you don't need all 100 methods (or however many there are), then leave them out. If you want a custom object from a method, then define a complexType that corresponds to that object. This is much simpler and more reliable than hand-authoring XML deserialization code for each method.
If you don't like that idea, and want to stick with manually writin the XML deserialization code, then you need to do two things:
attach a namespace to the XmlRoot attribute.
change the name of your class to ResponseExt, and derive it from a class called Response. Decorate that Response class with an XmlInclude attribute. This aligns the use of the Xml Serializer with the xsi:type used in the XML fragment.
It looks like this in code:
[XmlRoot("Response", Namespace="http://www.thirdparty.com/lr/")]
public class ResponseExt : Response {
}
[XmlRoot("Response", Namespace="http://www.thirdparty.com/lr/")]
[XmlInclude(typeof(ResponseExt))]
public class Response {
public string Code {get; set;}
public string Message {get; set;}
public string SessionId {get; set;}
}
public class XsiType
{
public static void Main(string[] args)
{
try
{
string filename = "XsiType.xml";
XmlSerializer s1 = new XmlSerializer(typeof(Response));
ResponseExt r = null;
using(System.IO.StreamReader reader= System.IO.File.OpenText(filename))
{
r= (ResponseExt) s1.Deserialize(reader);
}
var builder = new System.Text.StringBuilder();
var xmlws = new System.Xml.XmlWriterSettings { OmitXmlDeclaration = true, Indent= true };
using ( var writer = System.Xml.XmlWriter.Create(builder, xmlws))
{
//s1.Serialize(writer, r, ns);
s1.Serialize(writer, r);
}
string xml = builder.ToString();
System.Console.WriteLine(xml);
}
catch (System.Exception exc1)
{
Console.WriteLine("Exception: {0}", exc1.ToString());
}
}
}
related: How can I force the use of an xsi:type attribute?