Showing posts with label XPath. Show all posts
Showing posts with label XPath. Show all posts

Monday, January 11, 2010

Using XPath to manage data returned by a SharePoint Web Service

Topic:

Using XPath to navigate through elements and attributes in an XML document returned  by a SharePoint Web Service. 
This article proposes some examples which will show to developers how to use XPath in spite of namespaces present in the XML returned by Web services of SharePoint

1 - Business benefits (the "why" part of the post)

SharePoint provides Web Services that can be consumed in order for example to get data from a SharePoint Site, and display them within another web site or a desktop application.
It is a part of what is commonly called "SOA" in companies.
On the other hand, this principle can be applied to retrieve data from SharePoint Internet facing web site so as they can be a part of a mashup in a web site.

2- Development benefits

XPath allows developers to quickly navigate through elements and attributes in an XML document. It is very useful when there is no namespaces in the parsed XML, but it becomes more difficult when namespaces are used in the XML, especially if there is several namespaces and that they are present at different locations within the XML code.

2- Common examples

To give common examples, we are going to use the Lists Web Service and two of its methods.
This is a short Microsoft documentation excerpt and a link to it:

Lists Web Service
The Lists Web service provides methods for working with SharePoint lists, content types, list items, and files.
To access this Web service set a Web reference to

http://<site>/_vti_bin/Lists.asmx

 - The GetListCollection Method returns the names and GUIDs for all lists in a specific web site, and the complete SOAP XML returned by the web service looks like this one:

Request result for SharePoint Web Service /_vti_bin/Lists.asmx GetList

<?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>

    <GetListCollectionResponse xmlns='http://schemas.microsoft.com/sharepoint/soap/'>

      <GetListCollectionResult>

        <Lists>

          <List DocTemplateUrl='' DefaultViewUrl='/product/Lists/Demos/AllItems.aspx' MobileDefaultViewUrl='' ID='{91465226-671A-42EC-98ED-52C8C13403A1}' Title='D…

          <List DocTemplateUrl='/product/Documents/Forms/template.doc' DefaultViewUrl='/product/Documents/Forms/AllItems.aspx' MobileDefaultViewUrl='' ID='{3A9CB2…

          <List DocTemplateUrl='' DefaultViewUrl='/product/PublishingImages/Forms/AllItems.aspx' MobileDefaultViewUrl='' ID='{CB49539A-F215-41D3-8675-8CB6A0FAE9E2…

          <List DocTemplateUrl='' DefaultViewUrl='/product/_catalogs/masterpage/Forms/AllItems.aspx' MobileDefaultViewUrl='' ID='{E5E53853-B2A1-414F-AD18-62B6B274…

          <List DocTemplateUrl='' DefaultViewUrl='/product/Lists/News and Reviews/AllItems.aspx' MobileDefaultViewUrl='' ID='{45A63226-979A-402A-A029-6115AF0BE184…

          <List DocTemplateUrl='' DefaultViewUrl='/product/Pages/Forms/AllItems.aspx' MobileDefaultViewUrl='' ID='{B61087A0-9E16-451D-8132-1038BFFB63F5}' Title='P…

          <List DocTemplateUrl='' DefaultViewUrl='/product/Lists/Web Casts/AllItems.aspx' MobileDefaultViewUrl='' ID='{70F8653B-D81E-4AEE-8336-5A55789EB717}' Titl…

          <List DocTemplateUrl='' DefaultViewUrl='/product/WorkflowTasks/AllItems.aspx' MobileDefaultViewUrl='' ID='{75C8921E-9214-4212-998C-7AF611020D8D}' Title=…

        </Lists>

      </GetListCollectionResult>

    </GetListCollectionResponse>

  </soap:Body>

</soap:Envelope>


Now this is the first example that shows how to manage the namespaces concern with XPath. We will retrieve all the nodes corresponding to the SharePoint lists:

            // Create a new XmlDocument  

            XmlDocument doc = new XmlDocument();

 

            // Load data  

            doc.LoadXml(XmlSring);

            // define namespaces  

            XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);

            ns.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");

            ns.AddNamespace("namespace", "http://schemas.microsoft.com/sharepoint/soap/");

 

            // retrieve the lists nodes  

            XmlNodeList Lists = doc.SelectNodes("//soap:Envelope/soap:Body/namespace:GetListCollectionResponse/namespace:GetListCollectionResult/namespace:Lists", ns);


 - The GetListItems Method returns information about items in the list based on the specified query and the complete SOAP XML returned by the web service looks like this one:

<?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/XMLS...

    <soap:Body>

        <GetListItemsResponse xmlns="http://schemas.microsoft.com/sharepoint/soap/">

            <GetListItemsResult>

                <listitems xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882' xmlns:dt='uuid:C2F41010-65B3-11d1-A29F-00AA00C14882' xmlns:rs='urn:schemas-microsof...

                    <rs:data ItemCount="6">

                        <z:row ows_SilverlightBackgroundURL='http://sharepoint.microsoft.com/PublishingImages/home-2010_beta_banner.jpg' ows_HyperlinkURL='http:// ...

                        <z:row ows_LinkTitle='Enterprise Social Computing with SharePoint' ows_SilverlightBackgroundURL='http://sharepoint.microsoft.com/Publishin...

                        <z:row ows_LinkTitle='Save Money with SharePoint' ows_SilverlightBackgroundURL='http://sharepoint.microsoft.com/PublishingImages/SP_News_S...

                        <z:row ows_LinkTitle='Introducing Microsoft Office 2010 Beta' ows_SilverlightBackgroundURL='http://sharepoint.microsoft.com/PublishingImag...

                        <z:row ows_LinkTitle='Microsoft Office for the Mac SP2' ows_SilverlightBackgroundURL='http://sharepoint.microsoft.com/PublishingImages/SP_...

                        <z:row ows_SilverlightBackgroundURL='http://msn.com' ows_HyperlinkURL='http://msn.com, http://msn.com' ows_HyperlinkXoffset='-50' ows_Hype..

                    </rs:data>

                </listitems>

            </GetListItemsResult>

        </GetListItemsResponse>

    </soap:Body>

</soap:Envelope>


This is the second example that shows how to manage the namespaces concern with XPath. We will retrieve all the nodes corresponding to the items of a SharePoint list:

            // Create a new XmlDocument  

            XmlDocument doc = new XmlDocument();

 

            // Load data  

            doc.LoadXml(XmlString);

 

            // define namespaces  

            XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);

 

            ns.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");

            ns.AddNamespace("namespace", "http://schemas.microsoft.com/sharepoint/soap/");

            ns.AddNamespace("rs", "urn:schemas-microsoft-com:rowset");

 

            // retrieve the list items nodes  

            XmlNodeList data = doc.SelectNodes("//soap:Envelope/soap:Body/namespace:GetListItemsResponse/namespace:GetListItemsResult/namespace:listitems/rs:data", ns);

 

Wednesday, October 29, 2008

Read data stored in the custom section of a SharePoint web.config file using XPath

Introduction
In order to store Data shared by all sites, easily usable by server code programming and easy to deploy, some SharePoint Application needs custom entries in the Web Application web.config file. One simple solution is to add a line with a key and a value in the appSettings section.
However, you may need to customize your web.config file with a more complex and structured section. They are many post on how to add a custom section in a web.config for a SharePoint application using programmatically.
then, there is also code sample that shows how to read datas stored in the custom section in order to use them while programming.
Maybe the more complet sample is this one for Asp .Net but usable in SharePoint:

How To Create a Custom ASP.NET Configuration Section Handler in Visual C# .NET

I did practice this solution, but think it is a bit heavy. Then I wondered if it would not be more simple to use xpath to read the data sotred in the Custom ASP.NET Configuration Section of the SharePoint Application Web.config file.
1 - Exposing the business case
To make a clear demonstration, asume we are working for a company that owns several stores, and that the employees want to access to the company SharePoint intranet with Form Based Authentication Mode. We want to manage FBA profiles in order to map FBA roles with the SharePoint Portal application functionnalities like:
  • Access to a knowledge base
  • Make in line ordering
  • Edit order tracking reports
  • etc.

The FBA roles are the followings;
  • employee,
  • manager
  • owner
Thus, we want to store these FBA roles and Features in our SharePoint Portal web.config file with more technical data as aspnet FBA database connection string name, and we are going first to modify the web.config file
2 - Add a custom section to the SharePoint Portal web.config file
We open the web.config file (after having made a back-up) and add a section group to the configSection section.
    <sectionGroup name="System.Workflow.ComponentModel.WorkflowCompiler" type="System.Workflow.ComponentModel.Compiler.WorkflowCompilerConfigurationSectionGroup, System.Workflow.ComponentModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
      <section name="authorizedTypes" type="System.Workflow.ComponentModel.Compiler.AuthorizedTypesSectionHandler, System.Workflow.ComponentModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </sectionGroup>
<!--start adding -->
    <sectionGroup name="myPortal" >
      <section name="profileManagement" type="System.Configuration.SingleTagSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    </sectionGroup>
<!-- end adding -->
  </configSections>
Then, we add the "myPortal custom section
    <machineKey validationKey="80464397F43642274573BDB3C8C49CA32AD2DD99A86B46EC" decryptionKey="75DF7F2B93BE8584983E065036615D4BB091D5B3C8A668C8" validation="SHA1" />
    <sessionState mode="SQLServer" timeout="60" allowCustomSqlDatabase="true" partitionResolverType="Microsoft.Office.Server.Administration.SqlSessionStateResolver, Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
  </system.web>
<!--start adding -->
   <myPortal>
    <profileManagement connectionStringName="AspNetDbFBAConnectionString" applicationName="/" baseStoreFeatures="15" enableSessionState="false">
      <mainRoles>
        <role name="Owner" hasAllFeatures="true" />
        <role name="Manager" hasAllFeatures="true" />
        <role name="Employee" hasAllFeatures="false" />
      </mainRoles>
      <storeFeatures>
        <storeFeature name="PersonalizedHomePage" value="1" />
        <storeFeature name="ContactForm" value="2" />
        <storeFeature name="KnowledgeBase" value="4" default="true" />
        <storeFeature name="MyContacts" value="8" default="true" />
        <storeFeature name="Ordering" value="16" />
        <storeFeature name="Tracking" value="32" />
        <storeFeature name="BusinessReport" value="64" />
        <storeFeature name="FullMask" value="65535" />
      </storeFeatures>
    </profileManagement>
  </myPortal>
<!-- end adding -->
 <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
3 - Reading data stored in the web.config file
In order to read data stored in the web.config file, we are going to use the XMLExplorateur class that I introduced in a previous post:

Simplify XPath using a C# Class

and that I had completed to be more powerfull.
here is the class code:
        class XMLExplorateur
        {
            protected XPathDocument docNav;
            protected XPathNavigator nav;
            protected XPathNodeIterator xit;
            protected bool initpath = true;
            public XMLExplorateur() { }

            public XMLExplorateur(String path)
            {
                try
                {
                    docNav = new XPathDocument(path);
                    nav = docNav.CreateNavigator();
                }
                catch
                {
                    docNav = null;
                    nav = null;
                }
            }
            public bool Init(String path)
            {
                try
                {
                    docNav = new XPathDocument(path);
                    nav = docNav.CreateNavigator();
                }
                catch
                {
                    docNav = null;
                    nav = null;
                    return false;
                }
                return true;
            }

            public List<string> ValuesOf(String Item)
            {
                List<string> myList = new List<string>();
                if (nav == null) return null;
                String tmp = "descendant::" + Item;
                try
                {
                    xit = nav.Select(tmp);
                    while (xit.MoveNext())
                    {
                        myList.Add(xit.Current.Value);
                    }
                }
                catch
                {
                    myList = null;
                }
                return myList;
            }

            public Dictionary<string, uint> DictionaryStringUintItemsOf(String Item, string att1, string att2)
            {
                Dictionary<string, uint> myDictionary = new Dictionary<string, uint>();
                if (nav == null) return null;
                String tmp = "descendant::" + Item;
                try
                {
                    xit = nav.Select(tmp);
                    while (xit.MoveNext())
                    {
                        myDictionary.Add(xit.Current.GetAttribute(att1, ""), Convert.ToUInt32(xit.Current.GetAttribute(att2, "")));
                    }
                }
                catch
                {
                    myDictionary = null;
                }
                return myDictionary;
            }

            public Dictionary<string, bool> DictionaryStringBoolItemsOf(String Item, string att1, string att2)
            {
                Dictionary<string, bool> myDictionary = new Dictionary<string, bool>();
                if (nav == null) return null;
                String tmp = "descendant::" + Item;
                try
                {
                    xit = nav.Select(tmp);
                    while (xit.MoveNext())
                    {
                        myDictionary.Add(xit.Current.GetAttribute(att1, ""), Convert.ToBoolean(xit.Current.GetAttribute(att2, "")));
                    }
                }
                catch
                {
                    myDictionary = null;
                }
                return myDictionary;
            }

            public String ValueOf(String Item)
            {
                if (nav == null) return "Erreur Navigateur null";
                String tmp = "descendant::" + Item;
                try
                {
                    xit = nav.Select(tmp);
                    if (xit.MoveNext()) tmp = xit.Current.Value;
                    else tmp = "null";
                }
                catch
                {
                    tmp = "null";
                }
                return tmp;
            }
        }

Now, this is the way we can program to read all the data stored in the custom section :
        private static string _connectionStringName = null;
        private static string _applicationName = null;
        private static uint _baseStoreFeatures = uint.MinValue;
        private static Dictionary<string, uint> _availableStoreFeatures = null;
        private static Dictionary<string, bool> _mainRoles = null;
        private static bool _enableSessionState = false;


        void Page_Load(object sender, EventArgs e)
        {
            XMLExplorateur xe = new XMLExplorateur();

            Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(
                delegate()
                {
                    xe.Init(HttpContext.Current.Server.MapPath("~/web.config"));
                }
            );

            _connectionStringName = xe.ValueOf("configuration/myPortal/profileManagement/@connectionStringName");

            _applicationName = xe.ValueOf("configuration/myPortal/profileManagement/@applicationName");
            
            _baseStoreFeatures = Convert.ToUInt32(xe.ValueOf("configuration/myPortal/profileManagement/@baseStoreFeatures"));

            _enableSessionState = Convert.ToBoolean(xe.ValueOf("configuration/myPortal/profileManagement/@enableSessionState"));

            _availableStoreFeatures = xe.DictionaryStringUintItemsOf("configuration/myPortal/profileManagement/storeFeatures/storeFeature", "name", "value");

            _mainRoles = xe.DictionaryStringBoolItemsOf("configuration/myPortal/profileManagement/mainRoles/role", "name", "hasAllFeatures");

        }

Here is the complet code of the .aspx page. As it is an application page, place this page in the LAYOUTS directory, and call it from any site of your portal using this url (assume you call the page _readwebconfig.aspx as I did):
http://hostheader/anysite/anysubsite/_layouts/_readwebconfig.aspx

<%@ Page Language="C#" AutoEventWireup="true"  %>

<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls"  Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"  %> 
<%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"  %>
<%@ Import Namespace="Microsoft.SharePoint"  %>
<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages"  %>
<%@ Import Namespace="System.Collections.Generic"  %>
<%@ Import Namespace="System.Text"  %>
<%@ Import Namespace="System.Xml"  %>
<%@ Import Namespace="System.Xml.XPath"  %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title  ></title>
</head>
<body>

    <script runat="server" id="class_XMLExplorateur">

        class XMLExplorateur
        {
            protected XPathDocument docNav;
            protected XPathNavigator nav;
            protected XPathNodeIterator xit;
            protected bool initpath = true;
            public XMLExplorateur() { }

            public XMLExplorateur(String path)
            {
                try
                {
                    docNav = new XPathDocument(path);
                    nav = docNav.CreateNavigator();
                }
                catch
                {
                    docNav = null;
                    nav = null;
                }
            }
            public bool Init(String path)
            {
                try
                {
                    docNav = new XPathDocument(path);
                    nav = docNav.CreateNavigator();
                }
                catch
                {
                    docNav = null;
                    nav = null;
                    return false;
                }
                return true;
            }

            public List<string> ValuesOf(String Item)
            {
                List<string> myList = new List<string>();
                if (nav == null) return null;
                String tmp = "descendant::" + Item;
                try
                {
                    xit = nav.Select(tmp);
                    while (xit.MoveNext())
                    {
                        myList.Add(xit.Current.Value);
                    }
                }
                catch
                {
                    myList = null;
                }
                return myList;
            }

            public Dictionary<string, uint> DictionaryStringUintItemsOf(String Item, string att1, string att2)
            {
                Dictionary<string, uint> myDictionary = new Dictionary<string, uint>();
                if (nav == null) return null;
                String tmp = "descendant::" + Item;
                try
                {
                    xit = nav.Select(tmp);
                    while (xit.MoveNext())
                    {
                        myDictionary.Add(xit.Current.GetAttribute(att1, ""), Convert.ToUInt32(xit.Current.GetAttribute(att2, "")));
                    }
                }
                catch
                {
                    myDictionary = null;
                }
                return myDictionary;
            }

            public Dictionary<string, bool> DictionaryStringBoolItemsOf(String Item, string att1, string att2)
            {
                Dictionary<string, bool> myDictionary = new Dictionary<string, bool>();
                if (nav == null) return null;
                String tmp = "descendant::" + Item;
                try
                {
                    xit = nav.Select(tmp);
                    while (xit.MoveNext())
                    {
                        myDictionary.Add(xit.Current.GetAttribute(att1, ""), Convert.ToBoolean(xit.Current.GetAttribute(att2, "")));
                    }
                }
                catch
                {
                    myDictionary = null;
                }
                return myDictionary;
            }

            public String ValueOf(String Item)
            {
                if (nav == null) return "Erreur Navigateur null";
                String tmp = "descendant::" + Item;
                try
                {
                    xit = nav.Select(tmp);
                    if (xit.MoveNext()) tmp = xit.Current.Value;
                    else tmp = "null";
                }
                catch
                {
                    tmp = "null";
                }
                return tmp;
            }
        }
    </script>

    <script runat="server" id="method">
        
        private static string _connectionStringName = null;
        private static string _applicationName = null;
        private static uint _baseStoreFeatures = uint.MinValue;
        private static Dictionary<string, uint> _availableStoreFeatures = null;
        private static Dictionary<string, bool> _mainRoles = null;
        private static bool _enableSessionState = false;


        void Page_Load(object sender, EventArgs e)
        {
            //XmlDocument document = new XmlDocument();
            XMLExplorateur xe = new XMLExplorateur();

            Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(

                delegate()
                {
                    xe.Init(HttpContext.Current.Server.MapPath("~/web.config"));
                }

            );

            _connectionStringName = xe.ValueOf("configuration/myPortal/profileManagement/@connectionStringName");
            Response.Write("<br />_connectionStringName: " + _connectionStringName);

            _applicationName = xe.ValueOf("configuration/myPortal/profileManagement/@applicationName");
            Response.Write("<br />_applicationName: " + _applicationName);
            
            
            _baseStoreFeatures = Convert.ToUInt32(xe.ValueOf("configuration/myPortal/profileManagement/@baseStoreFeatures"));
            Response.Write("<br />_baseStoreFeatures:" + " " + _baseStoreFeatures.ToString());

            _enableSessionState = Convert.ToBoolean(xe.ValueOf("configuration/myPortal/profileManagement/@enableSessionState"));
            Response.Write("<br />_enableSessionState:" + " " + _enableSessionState.ToString());

            _availableStoreFeatures = xe.DictionaryStringUintItemsOf("configuration/myPortal/profileManagement/storeFeatures/storeFeature", "name", "value");
            Response.Write("<br /><br />_availableStoreFeatures:");
            foreach (string key in _availableStoreFeatures.Keys)
            {
                Response.Write("<br />Key: " + key + "Value:" + _availableStoreFeatures[key].ToString());
            }

            _mainRoles = xe.DictionaryStringBoolItemsOf("configuration/myPortal/profileManagement/mainRoles/role", "name", "hasAllFeatures");
            Response.Write("<br /><br />_mainRoles:");
            foreach (string key in _mainRoles.Keys)
            {
                Response.Write("<br />Key: " + key + " Value:" + _mainRoles[key].ToString());
            }
        }
        
    </script>
</body>
</html>
You should obtain that result when you call the page.



Sunday, October 5, 2008

Read an XML File inside a SharePoint Feature with simplified XPath using a C# Class

While working with SharePoint Customization using WSS object model, we sometimes need to read XML files. A good way to do it is to use XPath. It is more much usable if you simplify its use by writing a C# Class like the following one :
This class allows you to retrieve a single node value or multiple nodes values :
      public class XMLExplorateur
    {
        protected XPathDocument docNav;
        protected XPathNavigator nav;
        protected XPathNodeIterator xit;
        protected bool initpath = true;
        public XMLExplorateur() { }

        public XMLExplorateur(String path)
        {
            try
            {
                docNav = new XPathDocument(path);
                nav = docNav.CreateNavigator();
            }
            catch
            {
                docNav = null;
                nav = null;
            }
        }
        public bool Init(String path)
        {
            try
            {
                docNav = new XPathDocument(path);
                nav = docNav.CreateNavigator();
            }
            catch
            {
                docNav = null;
                nav = null;
                return false;
            }
            return true;
        }

        public List<string> ValuesOf(String Item)
        {
            List<string> myList = new List<string>();
            if (nav == null) return null;
            String tmp = "descendant::" + Item;
            try
            {
                xit = nav.Select(tmp);
                while (xit.MoveNext())
                {
                    myList.Add(xit.Current.Value);
                }
            }
            catch
            {
                myList = null;
            }
            return myList;
        }

        public String ValueOf(String Item)
        {
            if (nav == null) return "Erreur Navigateur null";
            String tmp = "descendant::" + Item;
            try
            {
                xit = nav.Select(tmp);
                if (xit.MoveNext()) tmp = xit.Current.Value;
                else tmp = "null";
            }
            catch
            {
                tmp = "null";
            }
            return tmp;
        }
    }
And here is the way to use it : I asume the xml file is inside a feature directory and I want to get informations from this file in my feature FeatureActivated method :
                protected XMLExplorateur xe=new XMLExplorateur();
                public override void FeatureActivated(SPFeatureReceiverProperties properties)
                {
                        string[] myDirectoryTable = System.IO.Directory.GetFiles(properties.Definition.RootDirectory + @"\Files");
                        String myFileCompletePath = myDirectoryTable[0];
                        xe.Init(myFileCompletePath);

                        myCustomer.FirstName = xe.ValueOf("Customer/FirstName"));
                        myCustomer.LastName= xe.ValueOf("Customer/LastName");

                        List<string> SalesComments = xe.ValuesOf("Customer/SalesComment");
                        foreach (string aComment in SalesComments)
                        {
                            myCustomer.SalesComments.Add(aComment);
                        }
                }
And of course the used xml file content.
<Customer>
    <FirstName>John</FirstName>
    <LastName>Doe</LastName>
    <LatestPurchasseDate>05/10/2008 13:37</LatestPurchasseDate>
    <SalesComment id="1">To be called back next week</SalesComment>
    <SalesComment id="2">was very interested by Mr. Smith porposal</SalesComment>
</Customer>
You will find MSDN link to study XPath syntax : here.