Query XML with Namespaces using XPathNavigator

2 09 2009

Querying XML data which has namespace qualifications can be a little confusing at times, consider the following document;

<query xmlns="http://platinumdogs.com/schema/reporting/query">
 <applicationid value=" CA6E1CAE-A74F-4542-9391-82DF90301B28" />
 <prf:mdschemaid value=" 25c36533-6e4b-4034-921c-bcc7a18e1ac5" xmlns:prf="http://platinumdogs.com/schema/metadata/query" />
 <mdschemaversion value=" 1" />
 <pagesize value=" 10" />
 <numberofpagesrequired value=" 1" />
 <pageindex value=" 1" />
 <startrow value=" 1" />
 <enablepagedaccess value=" True" />
 <ydimension value=" AGE_BAND_10_YEAR" />
 <xdimension value=" AGE_BAND_05_YEAR" />
 <xdimension_limit value=" 0" />
 <fact_measure value=" NUMBER_OF_DEATHS" />
</query>

This example is declaring a default namespace qualification at the root of the document, which scopes the inner part of the document to the default namespace, which in this case is http://platinumdogs.com/schema/reporting/query.

Given this example you might expect this code to return the <applicationid> node.

	var xnQuery = new XPathDocument(new XmlTextReader(new StringReader(myXmlString))).CreateNavigator();
	var xnExpressions = xnQuery.Select("/query/applicationid");
	Console.WriteLine("Node Count = " + xnExpressions.Count.ToString());

But it doesn’t return anything at all because we haven’t told the the XPathNavigator about the namespace used in the XML document. To do that we need to create an XmlNamespaceManager object, add the namespace declaration to it and provide this to the XPathNavigator.Select method, as shown below.

	var xnQuery = new XPathDocument(new XmlTextReader(new StringReader(myXmlString))).CreateNavigator();

	var ns = new XmlNamespaceManager(xnQuery.NameTable);
	ns.AddNamespace("ns0", "http://platinumdogs.com/schema/reporting/query");

	var xnExpressions = xnQuery.Select("/ns0:query/ns0:applicationid", ns);
	Console.WriteLine("Node Count = " + xnExpressions.Count.ToString());

Notice that I’m using the namespace prefix "ns0" in my XPath query, while the XML document declares the default namespace. This doesn’t matter because the important part is the Namespace URI (“http://platinumdogs.com/schema/reporting/query”) itself, the prefix part (whether explicit or the default) is effectivly a key or alias.

If the document had been declared using an explicit namespace declaration, say xmlns:ns="http://platinumdogs.com/schema/reporting/query", I could still use the "ns0" prefix in my XPath query.

So the Namespace prefix doesn’t matter, but doesn’t the XPathDocument know all about the NamespaceURI’s declared in the document? It does, but you still need to provide the list of NamespaceURI’s to the XPath query processor because it’s of relevance to the XPath query you specify in your call to the XPathNavigator.Select method. 

You can create an XmlNamespaceManager object and populate it using the Namespace declarations found in a XPathDocument. This is usefull when you don’t know what NamespaceURI’s you’re going to come across, the following code snippet shows how to do this.

	var xnQuery = new XPathDocument(new XmlTextReader(new StringReader(myXmlString))).CreateNavigator();

	var ns = new XmlNamespaceManager(xnQuery.NameTable);
	var nodes = xnQuery.Select("//*");

	while (nodes.MoveNext())
	{
		var nsis = nodes.Current.GetNamespacesInScope(XmlNamespaceScope.Local);
		foreach (var nsi in nsis)
		{
			var prf = nsi.Key == string.Empty ? "global" : nsi.Key;
			ns.AddNamespace(prf, nsi.Value);
			Console.WriteLine("Adding... prefix=" + prf + ", uri="+nsi.Value);
		}
	}

	var xnExpressions = xnQuery.Select("/global:query/global:applicationid", ns);
	Console.WriteLine("Node Count = " + xnExpressions.Count);

	xnExpressions = xnQuery.Select("/global:query/prf:mdschemaid", ns);
	Console.WriteLine("Node Count = " + xnExpressions.Count);

The code queries the document for all nodes, iterates over those nodes, gets the Namespaces in Scope and adds the declaration to the XmlNamespaceManager.  Notice how the default namespace is handled by substituting "global" for the default namespace prefix which is an empty string.





.NET (C#) Impersonation with Network Credentials

30 10 2008

I required a C# class to enable ad-hoc user account impersonation for accessing resources both on the local machine and also on network machines, which I’ve reproduced here.

Of note, if you require impersonation in order to access network resources, you would intuitively select the logon type of LOGON32_LOGON_NETWORK, this however doesn’t work, as according to MSDN, this type of logon is used for fast authentication where the credentials are not added to the local credential cache.

If you require the impersonated logon to have network credentials, you must select LOGON32_LOGON_NEW_CREDENTIALS as your logon type, which requires that you select LOGON32_PROVIDER_WINNT50 as the logon provider type.

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;

namespace Tools.Network
{
 public enum LogonType
 {
  LOGON32_LOGON_INTERACTIVE = 2,
  LOGON32_LOGON_NETWORK = 3,
  LOGON32_LOGON_BATCH = 4,
  LOGON32_LOGON_SERVICE = 5,
  LOGON32_LOGON_UNLOCK = 7,
  LOGON32_LOGON_NETWORK_CLEARTEXT = 8, // Win2K or higher
  LOGON32_LOGON_NEW_CREDENTIALS = 9 // Win2K or higher
 };

 public enum LogonProvider
 {
  LOGON32_PROVIDER_DEFAULT = 0,
  LOGON32_PROVIDER_WINNT35 = 1,
  LOGON32_PROVIDER_WINNT40 = 2,
  LOGON32_PROVIDER_WINNT50 = 3
 };

 public enum ImpersonationLevel
 {
  SecurityAnonymous = 0,
  SecurityIdentification = 1,
  SecurityImpersonation = 2,
  SecurityDelegation = 3
 }

 class Win32NativeMethods
 {
  [DllImport("advapi32.dll", SetLastError = true)]
  public static extern int LogonUser( string lpszUserName,
       string lpszDomain,
       string lpszPassword,
       int dwLogonType,
       int dwLogonProvider,
       ref IntPtr phToken);

  [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  public static extern int DuplicateToken( IntPtr hToken,
        int impersonationLevel,
        ref IntPtr hNewToken);

  [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  public static extern bool RevertToSelf();

  [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
  public static extern bool CloseHandle(IntPtr handle);
 }

 /// <summary>
 /// Allows code to be executed under the security context of a specified user account.
 /// </summary>
 /// <remarks> 
 ///
 /// Implements IDispose, so can be used via a using-directive or method calls;
 ///  ...
 ///
 ///  var imp = new Impersonator( "myUsername", "myDomainname", "myPassword" );
 ///  imp.UndoImpersonation();
 ///
 ///  ...
 ///
 ///   var imp = new Impersonator();
 ///  imp.Impersonate("myUsername", "myDomainname", "myPassword");
 ///  imp.UndoImpersonation();
 ///
 ///  ...
 ///
 ///  using ( new Impersonator( "myUsername", "myDomainname", "myPassword" ) )
 ///  {
 ///   ...
 ///   [code that executes under the new context]
 ///   ...
 ///  }
 ///
 ///  ...
 /// </remarks>
 public class Impersonator : IDisposable
 {
  private WindowsImpersonationContext _wic;

  /// <summary>
  /// Begins impersonation with the given credentials, Logon type and Logon provider.
  /// </summary>
  ///
<param name="userName">Name of the user.</param>
  ///
<param name="domainName">Name of the domain.</param>
  ///
<param name="password">The password. <see cref="System.String"/></param>
  ///
<param name="logonType">Type of the logon.</param>
  ///
<param name="logonProvider">The logon provider. <see cref="Mit.Sharepoint.WebParts.EventLogQuery.Network.LogonProvider"/></param>
  public Impersonator(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider)
  {
   Impersonate(userName, domainName, password, logonType, logonProvider);
  }

  /// <summary>
  /// Begins impersonation with the given credentials.
  /// </summary>
  ///
<param name="userName">Name of the user.</param>
  ///
<param name="domainName">Name of the domain.</param>
  ///
<param name="password">The password. <see cref="System.String"/></param>
  public Impersonator(string userName, string domainName, string password)
  {
   Impersonate(userName, domainName, password, LogonType.LOGON32_LOGON_INTERACTIVE, LogonProvider.LOGON32_PROVIDER_DEFAULT);
  }

  /// <summary>
  /// Initializes a new instance of the <see cref="Impersonator"/> class.
  /// </summary>
  public Impersonator()
  {}

  /// <summary>
  /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  /// </summary>
  public void Dispose()
  {
   UndoImpersonation();
  }

  /// <summary>
  /// Impersonates the specified user account.
  /// </summary>
  ///
<param name="userName">Name of the user.</param>
  ///
<param name="domainName">Name of the domain.</param>
  ///
<param name="password">The password. <see cref="System.String"/></param>
  public void Impersonate(string userName, string domainName, string password)
  {
   Impersonate(userName, domainName, password, LogonType.LOGON32_LOGON_INTERACTIVE, LogonProvider.LOGON32_PROVIDER_DEFAULT);
  }

  /// <summary>
  /// Impersonates the specified user account.
  /// </summary>
  ///
<param name="userName">Name of the user.</param>
  ///
<param name="domainName">Name of the domain.</param>
  ///
<param name="password">The password. <see cref="System.String"/></param>
  ///
<param name="logonType">Type of the logon.</param>
  ///
<param name="logonProvider">The logon provider. <see cref="Mit.Sharepoint.WebParts.EventLogQuery.Network.LogonProvider"/></param>
  public void Impersonate(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider)
  {
   UndoImpersonation();

   IntPtr logonToken = IntPtr.Zero;
   IntPtr logonTokenDuplicate = IntPtr.Zero;
   try
   {
    // revert to the application pool identity, saving the identity of the current requestor
    _wic = WindowsIdentity.Impersonate(IntPtr.Zero);

    // do logon & impersonate
    if (Win32NativeMethods.LogonUser(userName,
        domainName,
        password,
        (int)logonType,
        (int)logonProvider,
        ref logonToken) != 0)
    {
     if (Win32NativeMethods.DuplicateToken(logonToken, (int)ImpersonationLevel.SecurityImpersonation, ref logonTokenDuplicate) != 0)
     {
      var wi = new WindowsIdentity(logonTokenDuplicate);
      wi.Impersonate(); // discard the returned identity context (which is the context of the application pool)
     }
     else
      throw new Win32Exception(Marshal.GetLastWin32Error());
    }
    else
     throw new Win32Exception(Marshal.GetLastWin32Error());
   }
   finally
   {
    if (logonToken != IntPtr.Zero)
     Win32NativeMethods.CloseHandle(logonToken);

    if (logonTokenDuplicate != IntPtr.Zero)
     Win32NativeMethods.CloseHandle(logonTokenDuplicate);
   }
  }

  /// <summary>
  /// Stops impersonation.
  /// </summary>
  private void UndoImpersonation()
  {
   // restore saved requestor identity
   if (_wic != null)
    _wic.Undo();
   _wic = null;
  }
 }
}




Debugging .NET Serialization Code

25 02 2008

Just what is your XmlSerializer doing?

You can find out by debugging the serialization code which is generated automatically at runtime;

1. Modify your .config file to include the following snippet

<configuration>
   <system.diagnostics>
      <switches>
         <add name="XmlSerialization.Compilation" value="1" />
      </switches>
   </system.diagnostics>
</configuration>

2. Rebuild your code and set a breakpoint on or just after where you create an instance of the XmlSerializer, but before you call Serialize() or Deserialize().

3. Navigate to the temp directory in your profiles local settings directory

For Vista this is [C:\Users\{user}\AppDataLocalTemp]

For Win XP+ this is [C:\Documents and Settings\{user}\Local Settings\Temp]

4. In Visual Studio, open the most recent .cs file from this folder, set a break point and away you go.
 

Technorati Tags: ,,,,




Showing an Assemblies Fully Qualified Name

4 01 2008

To show the fully qualified name of an assembly you can use Lutz Roeders Reflector, or you can write a simple console application to do the same thing.

namespace showtypeinfo
{
   class Program
   {
      static void Main(string[] args)
      {
         if (args.Length < 1) {
            return;
         }

         Assembly asm = Assembly.LoadFrom(args[0].ToString());
         Console.WriteLine("\nShowTypeInfo v1.0\n=================\n");
         Console.WriteLine("\n   Assembly: {0}",args[0].ToString());
         Console.WriteLine("\n   FQN: {0}",asm.FullName.ToString());
      }
   }
}




Serialisation in C# 3.0

21 12 2007

This is a great article demonstrating Serialisation in C# 3.0 including the DataContractSerializer.

O’Reilly C# 3.0 in a Nutshell