I'm trying to integrate a WebDAV client into some bigger tool suite to be able to create events/notifications from my software in the users existing calendar. My project is a WPF application written in c#.
I have set up a calendar with a WebDAV interface/api available and now I try to read the ctag
property of the calendar. When sending the PROPFIND
http request
<?xml version="1.0" encoding="utf-8"?>
<d:propfind xmlns:d=\"DAV:\" xmlns:cs=\"http://calendarserver.org/ns/\">
<d:prop>
<d:displayname/>
<cs:getctag/>
</d:prop>
</d:propfind>
I receive a http response with the following content:
<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:" xmlns:nmm="http://all-inkl.com/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/">
<d:response>
<d:href>/calendars/cal0015dc8/1/</d:href>
<d:propstat>
<d:prop>
<d:displayname>My Calendar Name</d:displayname>
<cs:getctag>0</cs:getctag>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
I know that the namespaces might look a little suspicious, some with and some without a trailing slash /
, namespace d
even with a trailing colon :
, but this is exactly what I get from the server. If I for example change the namespace xmlns:d="DAV:"
in my request to xmlns:d="DAV"
, I get a response status 500: InternalServerError
, so I took the namespace declarations exactly as they are in the response.
Now, I want to get the value from the cs:getctag
node. Problem is, everything I tried always returns null
when navigating through the xml structure.
For clarification: response.Content.ReadAsStringAsync().Result
returns the afore mentioned response xml string.
First try: Load response in a XmlDocument and access the subnodes by namespace/name combination:
using System.Xml;
XmlDocument doc = new XmlDocument();
XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(doc.NameTable);
xmlNamespaceManager.AddNamespace("d", "DAV:");
xmlNamespaceManager.AddNamespace("nmm", "http://all-inkl.com/ns");
xmlNamespaceManager.AddNamespace("cal", "urn:ietf:params:xml:ns:caldav");
xmlNamespaceManager.AddNamespace("cs", "http://calendarserver.org/ns/");
doc.LoadXml(response.Content.ReadAsStringAsync().Result);
XmlNode root = doc.DocumentElement;
XmlNode ctagNode = root["response", "d"]["propstat", "d"]["prop", "d"]["getctag", "cs"];
ctag = Convert.ToInt64(ctagNode.InnerText);
The node root
is correctly set to element <d:multistatus>
, but in the next line, where ctagNode
should get selected, the code throws an exception:
System.NullReferenceException: Object reference not set to an instance of an object.
Second Try: Get the node with a XPath selection
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
XmlReader xmlReader = XmlReader.Create(new StringReader(response.Content.ReadAsStringAsync().Result));
XmlNamespaceManager nsManager = new XmlNamespaceManager(xmlReader.NameTable);
nsManager.AddNamespace("d", "DAV:");
nsManager.AddNamespace("nmm", "http://all-inkl.com/ns");
nsManager.AddNamespace("cal", "urn:ietf:params:xml:ns:caldav");
nsManager.AddNamespace("cs", "http://calendarserver.org/ns/");
XDocument myXDocument = XDocument.Load(xmlReader);
XPathNavigator myNavigator = myXDocument.CreateNavigator();
string query = "//d:multistatus/d:response/d:propstat/d:prop/cs:getctag";
XPathNavigator ctagElement = myNavigator.SelectSingleNode(query, nsManager);
ctag = ctagElement.ValueAsLong;
After the execution of XPathNavigator ctagElement = myNavigator.SelectSingleNode(query, nsManager);
, the object ctagElement
is still null.
Can someone point out what I'm doing wrong in either case (1-Bare xml, 2-XPath) and how to do it right?
I would appreciate answers that help me solve this problem and that generally help me understand how to correctly navigate in xml data. You're welcome to also link to a comprehensive documentation or tutorial.
As @GSerg pointed out in his comment to my question, I was indeed not using the XmlNamespaceManager
I have created in my First Try solution.
As it turns out, in my code example was just one small mistake:
using System.Xml;
XmlDocument doc = new XmlDocument();
XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(doc.NameTable);
xmlNamespaceManager.AddNamespace("d", "DAV:");
xmlNamespaceManager.AddNamespace("nmm", "http://all-inkl.com/ns");
xmlNamespaceManager.AddNamespace("cal", "urn:ietf:params:xml:ns:caldav");
xmlNamespaceManager.AddNamespace("cs", "http://calendarserver.org/ns/");
doc.LoadXml(response.Content.ReadAsStringAsync().Result);
XmlNode root = doc.DocumentElement;
// THIS LINE WAS WRONG
XmlNode ctagNode = root["response", "d"]
["propstat", "d"]
["prop", "d"]
["getctag", "cs"];
// IT SHOULD LOOK LIKE THIS:
XmlNode ctagNode = root["response", xmlNamespaceManager.LookupNamespace("d")]
["propstat", xmlNamespaceManager.LookupNamespace("d")]
["prop", xmlNamespaceManager.LookupNamespace("d")]
["getctag", xmlNamespaceManager.LookupNamespace("cs")];
ctag = Convert.ToInt64(ctagNode.InnerText);
Looks like the syntax
XmlNode childNode = parentNode["nameOfChildNode", "namespaceOfChildNode"]
requires the full namespace, not the namespace prefix.
As for my Second Try , I already used the namespace manager and the code worked after a VisualStudio restart and solution rebuild. No code change required.
Thank you @GSerg:-)
Try following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication186
{
class Program
{
const string FILENAME = @"c:\temp\test.xml";
static void Main(string[] args)
{
string xml = File.ReadAllText(FILENAME);
StringReader sReader = new StringReader(xml);
XmlReader xReader = XmlReader.Create(sReader);
XmlSerializer serializaer = new XmlSerializer(typeof(MultiStatus));
MultiStatus multiStatus = (MultiStatus)serializaer.Deserialize(xReader);
}
}
[XmlRoot(ElementName = "multistatus", Namespace = "DAV:")]
public class MultiStatus
{
[XmlElement(Namespace = "DAV:")]
public Response response { get; set; }
}
public class Response
{
[XmlElement(Namespace = "DAV:")]
public string href { get; set; }
[XmlElement(ElementName = "propstat", Namespace = "DAV:")]
public Propstat propstat { get; set; }
}
public class Propstat
{
[XmlElement(ElementName = "prop", Namespace = "DAV:")]
public Prop prop { get; set; }
[XmlElement(ElementName = "status", Namespace = "DAV:")]
public string status { get; set; }
}
public class Prop
{
[XmlElement(Namespace = "DAV:")]
public string displayname { get; set; }
[XmlElement(Namespace = "http://calendarserver.org/ns/")]
public string getctag { get; set; }
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.