简体   繁体   中英

C#/XML: XPathNavigator.SelectSingleNode() always returns null

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM