Automation FindFirst on TreeScope does not find element - automationelement

I have a problem with TreeScope finding an element in Internet Explorer when searching the Name property and ControlType property.
mainElement is the AutomationElement representing "internet Explorer_Server.
all automation elements are listed under mainElement in UISpy.
public Auto.AutomationElement GetElementByNameAndControlType(Auto.AutomationElement mainElement, System.Windows.Automation.ControlType controlType, string propertyName)
{
Auto.AutomationElement target = null;
Auto.PropertyCondition typeCondition1 = new Auto.PropertyCondition (Auto.AutomationElement.ControlTypeProperty, controlType);
Auto.PropertyCondition typeCondition2 = new Auto.PropertyCondition(Auto.AutomationElement.NameProperty, propertyName);
Auto.AndCondition andCondition2 = new Auto.AndCondition(typeCondition1, typeCondition2);
target = mainElement.FindFirst(Auto.TreeScope.Descendants, andCondition2);
return target;
}
I was finally able to find the element with the code below, but really want to understand why the code above didn't work.
public Auto.AutomationElement GetElementByIsValuePatternAvailablePropertyAndName(Auto.AutomationElement mainElement, string name, Auto.ControlType controlType)
{
Auto.AutomationElement target = null;
Auto.Condition conditions = new Auto.AndCondition(new Auto.PropertyCondition(Auto.AutomationElement.IsEnabledProperty, true),
new Auto.PropertyCondition(Auto.AutomationElement.IsValuePatternAvailableProperty, true));
// Find all children that match the specified conditions.
Auto.AutomationElementCollection elementCollection = mainElement.FindAll (Auto.TreeScope.Descendants, conditions);
foreach (Auto.AutomationElement ae in elementCollection)
{
if (ae.Current.Name == name)
{
target = ae;
break;
}
}
return target;
}

Please add assembly references of UIAutomationClient and UIAutomationTypes in your project ;-)
via martin's answer

Related

How to get out of repetitive if statements?

While looking though some code of the project I'm working on, I've come across a pretty hefty method which does
the following:
public string DataField(int id, string fieldName)
{
var data = _dataRepository.Find(id);
if (data != null)
{
if (data.A == null)
{
data.A = fieldName;
_dataRepository.InsertOrUpdate(data);
return "A";
}
if (data.B == null)
{
data.B = fieldName;
_dataRepository.InsertOrUpdate(data);
return "B";
}
// keep going data.C through data.Z doing the exact same code
}
}
Obviously having 26 if statements just to determine if a property is null and then to update that property and do a database call is
probably very naive in implementation. What would be a better way of doing this unit of work?
Thankfully C# is able to inspect and assign class members dynamically, so one option would be to create a map list and iterate over that.
public string DataField(int id, string fieldName)
{
var data = _dataRepository.Find(id);
List<string> props = new List<string>();
props.Add("A");
props.Add("B");
props.Add("C");
if (data != null)
{
Type t = typeof(data).GetType();
foreach (String entry in props) {
PropertyInfo pi = t.GetProperty(entry);
if (pi.GetValue(data) == null) {
pi.SetValue(data, fieldName);
_dataRepository.InsertOrUpdate(data);
return entry;
}
}
}
}
You could just loop through all the character from 'A' to 'Z'. It gets difficult because you want to access an attribute of your 'data' object with the corresponding name, but that should (as far as I know) be possible through the C# reflection functionality.
While you get rid of the consecutive if-statements this still won't make your code nice :P
there is a fancy linq solution for your problem using reflection:
but as it was said before: your datastructure is not very well thought through
public String DataField(int id, string fieldName)
{
var data = new { Z = "test", B="asd"};
Type p = data.GetType();
var value = (from System.Reflection.PropertyInfo fi
in p.GetProperties().OrderBy((fi) => fi.Name)
where fi.Name.Length == 1 && fi.GetValue(data, null) != null
select fi.Name).FirstOrDefault();
return value;
}
ta taaaaaaaaa
like that you get the property but the update is not yet done.
var data = _dataRepository.Find(id);
If possible, you should use another DataType without those 26 properties. That new DataType should have 1 property and the Find method should return an instance of that new DataType; then, you could get rid of the 26 if in a more natural way.
To return "A", "B" ... "Z", you could use this:
return (char)65; //In this example this si an "A"
And work with some transformation from data.Value to a number between 65 and 90 (A to Z).
Since you always set the lowest alphabet field first and return, you can use an additional field in your class that tracks the first available field. For example, this can be an integer lowest_alphabet_unset and you'd update it whenever you set data.{X}:
Init:
lowest_alphabet_unset = 0;
In DataField:
lowest_alphabet_unset ++;
switch (lowest_alphabet_unset) {
case 1:
/* A is free */
/* do something */
return 'A';
[...]
case 7:
/* A through F taken */
data.G = fieldName;
_dataRepository.InsertOrUpdate(data);
return 'G';
[...]
}
N.B. -- do not use, if data is object rather that structure.
what comes to my mind is that, if A-Z are all same type, then you could theoretically access memory directly to check for non null values.
start = &data;
for (i = 0; i < 26; i++){
if ((typeof_elem) *(start + sizeof(elem)*i) != null){
*(start + sizeof(elem)*i) = fieldName;
return (char) (65 + i);
}
}
not tested but to give an idea ;)

Dynamically Sorting with LINQ

I have a collection of CLR objects. The class definition for the object has three properties: FirstName, LastName, BirthDate.
I have a string that reflects the name of the property the collection should be sorted by. In addition, I have a sorting direction. How do I dynamically apply this sorting information to my collection? Please note that sorting could be multi-layer, so for instance I could sort by LastName, and then by FirstName.
Currently, I'm trying the following without any luck:
var results = myCollection.OrderBy(sortProperty);
However, I'm getting a message that says:
... does not contain a defintion for 'OrderBy' and the best extension method overload ... has some invalid arguments.
Okay, my argument with SLaks in his comments has compelled me to come up with an answer :)
I'm assuming that you only need to support LINQ to Objects. Here's some code which needs significant amounts of validation adding, but does work:
// We want the overload which doesn't take an EqualityComparer.
private static MethodInfo OrderByMethod = typeof(Enumerable)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(method => method.Name == "OrderBy"
&& method.GetParameters().Length == 2)
.Single();
public static IOrderedEnumerable<TSource> OrderByProperty<TSource>(
this IEnumerable<TSource> source,
string propertyName)
{
// TODO: Lots of validation :)
PropertyInfo property = typeof(TSource).GetProperty(propertyName);
MethodInfo getter = property.GetGetMethod();
Type propType = property.PropertyType;
Type funcType = typeof(Func<,>).MakeGenericType(typeof(TSource), propType);
Delegate func = Delegate.CreateDelegate(funcType, getter);
MethodInfo constructedMethod = OrderByMethod.MakeGenericMethod(
typeof(TSource), propType);
return (IOrderedEnumerable<TSource>) constructedMethod.Invoke(null,
new object[] { source, func });
}
Test code:
string[] foo = new string[] { "Jon", "Holly", "Tom", "William", "Robin" };
foreach (string x in foo.OrderByProperty("Length"))
{
Console.WriteLine(x);
}
Output:
Jon
Tom
Holly
Robin
William
It even returns an IOrderedEnumerable<TSource> so you can chain ThenBy clauses on as normal :)
You need to build an Expression Tree and pass it to OrderBy.
It would look something like this:
var param = Expression.Parameter(typeof(MyClass));
var expression = Expression.Lambda<Func<MyClass, PropertyType>>(
Expression.Property(param, sortProperty),
param
);
Alternatively, you can use Dynamic LINQ, which will allow your code to work as-is.
protected void sort_grd(object sender, GridViewSortEventArgs e)
{
if (Convert.ToBoolean(ViewState["order"]) == true)
{
ViewState["order"] = false;
}
else
{
ViewState["order"] = true;
}
ViewState["SortExp"] = e.SortExpression;
dataBind(Convert.ToBoolean(ViewState["order"]), e.SortExpression);
}
public void dataBind(bool ord, string SortExp)
{
var db = new DataClasses1DataContext(); //linq to sql class
var Name = from Ban in db.tbl_Names.AsEnumerable()
select new
{
First_Name = Ban.Banner_Name,
Last_Name = Ban.Banner_Project
};
if (ord)
{
Name = BannerName.OrderBy(q => q.GetType().GetProperty(SortExp).GetValue(q, null));
}
else
{
Name = BannerName.OrderByDescending(q => q.GetType().GetProperty(SortExp).GetValue(q, null));
}
grdSelectColumn.DataSource = Name ;
grdSelectColumn.DataBind();
}
you can do this with Linq
var results = from c in myCollection
orderby c.SortProperty
select c;
For dynamic sorting you could evaluate the string i.e. something like
List<MyObject> foo = new List<MyObject>();
string sortProperty = "LastName";
var result = foo.OrderBy(x =>
{
if (sortProperty == "LastName")
return x.LastName;
else
return x.FirstName;
});
For a more generic solution see this SO thread: Strongly typed dynamic Linq sorting
For this sort of dynamic work I've been using the Dynamic LINQ library which makes this sort of thing easy:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
http://msdn2.microsoft.com/en-us/vcsharp/bb894665.aspx
You can copy paste the method I post in that answer, and change the signature/method names:
How to make the position of a LINQ Query SELECT variable
You can actually use your original line of code
var results = myCollection.OrderBy(sortProperty);
simply by using the System.Linq.Dynamic library.
If you get a compiler error (something like cannot convert from or does not contain a definition...) you may have to do it like this:
var results = myCollection.AsQueryable().OrderBy(sortProperty);
No need for any expression trees or data binding.
You will need to use reflection to get the PropertyInfo, and then use that to build an expression tree. Something like this:
var entityType = typeof(TEntity);
var prop = entityType.GetProperty(sortProperty);
var param = Expression.Parameter(entityType, "x");
var access = Expression.Lambda(Expression.MakeMemberAccess(param, prop), param);
var ordered = (IOrderedQueryable<TEntity>) Queryable.OrderBy(
myCollection,
(dynamic) access);

WatiN LogonDialogHandlers not working correctly in Windows 7

I have recently updated to Windows 7, VS2010 and IE8. We have an automation suite running tests against IE using WatiN. These tests require the logon dialog handler to be used in order to log different AD Users into the IE Browser.
This works perfectly when using Windows XP and IE8, but now using Windows 7 has resulted in the Windows Security dialog box no longer being recognised, the dialogue box is just being ignored. This is the method being used to startup the browser:
public static Browser StartBrowser(string url, string username, string password)
{
Browser browser = new IE();
WatiN.Core.DialogHandlers.LogonDialogHandler ldh = new WatiN.Core.DialogHandlers.LogonDialogHandler(username, password);
browser.DialogWatcher.Add(ldh);
browser.GoTo(url);
return browser;
}
any suggestions or help would be greatly appreciated...
For whatever reason the code Clint posted had comments instead of entering the username, password and submitting, and referenced an undefined method, but is otherwise OK. Here's some completed (and working) code:
public static Browser Win7Login(string username, string password, string URL) {
Process ieProcess = Process.Start("iexplore.exe", URL);
ieProcess.WaitForInputIdle();
Thread.Sleep(2000);
AutomationElement ieWindow = AutomationElement.FromHandle(ieProcess.MainWindowHandle);
string t = ieWindow.Current.ClassName.ToString();
Condition conditions = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window));
Condition List_condition = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ListItem));
Condition Edit_condition = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
Condition button_conditions = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));
AutomationElementCollection c = ieWindow.FindAll(TreeScope.Children, conditions);
foreach (AutomationElement child in c) {
if (child.Current.ClassName.ToString() == "#32770") {
// find the list
AutomationElementCollection lists = child.FindAll(TreeScope.Children, List_condition);
// find the buttons
AutomationElementCollection buttons = child.FindAll(TreeScope.Children, button_conditions);
foreach (AutomationElement list in lists) {
if (list.Current.ClassName.ToString() == "UserTile") {
AutomationElementCollection edits = list.FindAll(TreeScope.Children, Edit_condition);
foreach (AutomationElement edit in edits) {
if (edit.Current.Name.Contains("User name")) {
edit.SetFocus();
ValuePattern usernamePattern = edit.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
usernamePattern.SetValue(username);
}
if (edit.Current.Name.Contains("Password")) {
edit.SetFocus();
ValuePattern passwordPattern = edit.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
passwordPattern.SetValue(password);
}
}
}
}
foreach (AutomationElement button in buttons) {
if (button.Current.AutomationId == "SubmitButton") {
InvokePattern submitPattern = button.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
submitPattern.Invoke();
break;
}
}
}
}
return IE.AttachTo<IE>(Find.By("hwnd", ieWindow.Current.NativeWindowHandle.ToString()), 30);
}
This can also be refactored as a DialogHandler like this:
public class Windows7LogonDialogHandler : BaseDialogHandler
{
private readonly string _username;
private readonly string _password;
AndCondition _conditions = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window));
readonly AndCondition _listCondition = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ListItem));
readonly AndCondition _editCondition = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
readonly AndCondition _buttonConditions = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));
public Windows7LogonDialogHandler(string username, string password)
{
_username = username;
_password = password;
}
public override bool HandleDialog(Window window)
{
if(CanHandleDialog(window))
{
var win = AutomationElement.FromHandle(window.Hwnd);
var lists = win.FindAll(TreeScope.Children, _listCondition);
var buttons = win.FindAll(TreeScope.Children, _buttonConditions);
var another = (from AutomationElement list in lists
where list.Current.ClassName == "UserTile"
where list.Current.Name == "Use another account"
select list).First();
another.SetFocus();
foreach (var edit in from AutomationElement list in lists
where list.Current.ClassName == "UserTile"
select list.FindAll(TreeScope.Children, _editCondition)
into edits
from AutomationElement edit in edits
select edit)
{
if (edit.Current.Name.Contains("User name"))
{
edit.SetFocus();
var usernamePattern = edit.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
if (usernamePattern != null) usernamePattern.SetValue(_username);
}
if (edit.Current.Name.Contains("Password"))
{
edit.SetFocus();
var passwordPattern = edit.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
if (passwordPattern != null) passwordPattern.SetValue(_password);
}
}
foreach (var submitPattern in from AutomationElement button in buttons
where button.Current.AutomationId == "SubmitButton"
select button.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern)
{
submitPattern.Invoke();
break;
}
return true;
}
return false;
}
public override bool CanHandleDialog(Window window)
{
return window.ClassName == "#32770";
}
}
Which is a little bit nicer. You can then use it like this:
using(var ie = new IE())
{
ie.DialogWatcher.Add(new Windows7LogonDialogHandler(#"domain\user", "password"));
ie.GoTo("http://mysecuredwebsite");
}
We have eventually solved this issue by using the Windows Automation 3.0 API to pick up the dialogue box and handle the logging in. This was done as follows:
Fire up an IE process and assign it to an AutomationElement
You now have the ability to traverse through the child elements of the IEFrame, picking up the username and password Edit fields.
Then send the username and password
Once the browser has been authenticated we then attach it to a WatiN IE browser object. The code follows below:
public static Browser LoginToBrowser(string UserName, string Password, string URL)
{
AutomationElement element = StartApplication("IEXPLORE.EXE", URL);
Thread.Sleep(2000);
string t = element.Current.ClassName.ToString();
Condition conditions = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window));
Condition List_condition = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ListItem));
Condition Edit_condition = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
Condition button_conditions = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));
AutomationElementCollection c = element.FindAll(TreeScope.Children, conditions);
foreach (AutomationElement child in c)
{
if (child.Current.ClassName.ToString() == "#32770")
{
//find the list
AutomationElementCollection lists = child.FindAll(TreeScope.Children, List_condition);
// find the buttons
AutomationElementCollection buttons = child.FindAll(TreeScope.Children, button_conditions);
foreach (AutomationElement list in lists)
{
if (list.Current.ClassName.ToString() == "UserTile")
{
AutomationElementCollection edits = list.FindAll(TreeScope.Children, Edit_condition);
foreach (AutomationElement edit in edits)
{
if (edit.Current.Name.Contains("User name"))
{
edit.SetFocus();
//send the user name
}
if (edit.Current.Name.Contains("Password"))
{
edit.SetFocus();
//send the password
}
}
}
}
foreach (AutomationElement button in buttons)
{
if (button.Current.AutomationId == "SubmitButton")
{
//click the button
break;
}
}
}
}
return IE.AttachToIE(Find.By("hwnd", element.Current.NativeWindowHandle.ToString()), 30) ;
}
We did use a tool called UI Spy to examine the Windows UI. If you run it against XP and Win7 you can clearly see how the structure of the Windows Security dialogue box has changed between the 2 OS's.
Nicholas Riley post works like a charm, however including the using System.Windows.Automation might be a little tricky. I thought microsoft would put this in the GAC, but they do not, at least for me running Windows 7 professional. I even downloaded the Automation Toolkit from here.
Turns out, there is a subject here on stack overflow that shows where the dll's are that you can browse to include as references in your project. The link for that is here.
Essentially, you just need to reference two dll's. UIAutomationClient.dll and UIAutomationTypes.dll (both in the same directory).
Since nobody answered your question, I will, but unfortunately without ready solution.
I don't have Windows 7 to try at this moment, but it seems that WatiN's LogonDialogHandler is not compatible with Windows 7, so you have to write your own DialogHandler. The simplest way is to inherit from BaseDialogHandler. You can look at the source code of existing dialog handlers in WatiN. I made my self very simple and not universal one for handling certificate dialog. WinSpy++ can be very useful during implementation.
If you set whatever process is running your watin to run as administrator in Windows 7, the DialogHandlers work just fine.
I tried to use the two automation examples above and found that they did not handle the scenario when the other credentials have been remembered in which case you only see the password in box. In this case you need to programmatically click the "Use another account" section. So I modified the supplied code to do that and it is now working OK. Here's the modified code:
public static Browser Win7Login(string username, string password, string url)
{
var ieProcess = Process.Start("iexplore.exe", url);
ieProcess.WaitForInputIdle();
Thread.Sleep(2000);
var ieWindow = AutomationElement.FromHandle(ieProcess.MainWindowHandle);
var conditions = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window));
var listCondition = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ListItem));
var editCondition = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
var buttonConditions = new AndCondition(new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));
var c = ieWindow.FindAll(TreeScope.Children, conditions);
foreach (AutomationElement child in c)
{
if (child.Current.ClassName == "#32770")
{
// find the list
var lists = child.FindAll(TreeScope.Children, listCondition);
// find the buttons
var buttons = child.FindAll(TreeScope.Children, buttonConditions);
var another = (from AutomationElement list in lists
where list.Current.ClassName == "UserTile"
where list.Current.Name == "Use another account"
select list).First();
another.SetFocus();
foreach (var edit in from AutomationElement list in lists
where list.Current.ClassName == "UserTile"
select list.FindAll(TreeScope.Children, editCondition)
into edits from AutomationElement edit in edits select edit)
{
if (edit.Current.Name.Contains("User name"))
{
edit.SetFocus();
var usernamePattern = edit.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
if (usernamePattern != null) usernamePattern.SetValue(username);
}
if (edit.Current.Name.Contains("Password"))
{
edit.SetFocus();
var passwordPattern = edit.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
if (passwordPattern != null) passwordPattern.SetValue(password);
}
}
foreach (var submitPattern in from AutomationElement button in buttons
where button.Current.AutomationId == "SubmitButton"
select button.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern)
{
submitPattern.Invoke();
break;
}
}
}
return IE.AttachTo<IE>(Find.By("hwnd", ieWindow.Current.NativeWindowHandle.ToString()), 30);
}
Thanks to the others who got me most of the way there.

Linq to Sql - Repository Pattern - Dynamic OrderBy

Ok, I found this, which will allow me to do this:
public IList<Item> GetItems(string orderbyColumn)
{
return _repository.GetItems().OrderBy(orderByColumn).ToList();
}
Is this the best way to do "dynamic" ordering? I want to be able to pass the column name as a string (and the sort direction) to my Service, and have it order the correct way.
That's certainly a viable way of doing dynamic sorting. Ch00k provided another option in his answer to this question about "Strongly typed dynamic Linq sorting". I personally prefer Ch00k's method, as there's some compile-time checking involved and there's very little extra code involved.
If you've already decided that it must be a string, then your options are somewhat limited. The Dynamic LINQ library would indeed do the job, or if you want t know how it all works, look at this previous answer which builds an Expression from the string at runtime.
At the moment the code only accepts a single member and has separate methods for ascending / descending, but from this example it should be fairly simple to pass a more complex string and split it; essentially as:
IQueryable<T> query = ...
string[] portions = orderBy.Split(' '); // split on space, arbitrarily
if(portions.Length == 0) throw new ArgumentException();
IOrderedQueryable<T> orderedQuery = query.OrderBy(portions[0]);
for(int i = 1 ; i < portions.Length ; i++) { // note we already did the zeroth
orderedQuery = orderedQuery.ThenBy(portions[i]);
}
return orderedQuery;
If you're just after dynamic sorting without the full Dynamic-Linq stuff you can check out a post I wrote about this a while back: click
EDIT: I don't really blog anymore so here's the actual extension method:
public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string sortExpression) where TEntity : class
{
if (string.IsNullOrEmpty(sortExpression))
return source; // nothing to sort on
var entityType = typeof(TEntity);
string ascSortMethodName = "OrderBy";
string descSortMethodName = "OrderByDescending";
string[] sortExpressionParts = sortExpression.Split(' ');
string sortProperty = sortExpressionParts[0];
string sortMethod = ascSortMethodName;
if (sortExpressionParts.Length > 1 && sortExpressionParts[1] == "DESC")
sortMethod = descSortMethodName;
var property = entityType.GetProperty(sortProperty);
var parameter = Expression.Parameter(entityType, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
MethodCallExpression resultExp = Expression.Call(
typeof(Queryable),
sortMethod,
new Type[] { entityType, property.PropertyType },
source.Expression,
Expression.Quote(orderByExp));
return source.Provider.CreateQuery<TEntity>(resultExp);
}

How to get the position() of an XElement?

Any XPath like /NodeName/position() would give you the position of the Node w.r.t it's parent node.
There is no method on the XElement (Linq to XML) object that can get the position of the Element. Is there?
Actually NodesBeforeSelf().Count doesn't work because it gets everything even of type XText
Question was about XElement object.
So I figured it's
int position = obj.ElementsBeforeSelf().Count();
that should be used,
Thanks to Bryant for the direction.
You could use the NodesBeforeSelf method to do this:
XElement root = new XElement("root",
new XElement("one",
new XElement("oneA"),
new XElement("oneB")
),
new XElement("two"),
new XElement("three")
);
foreach (XElement x in root.Elements())
{
Console.WriteLine(x.Name);
Console.WriteLine(x.NodesBeforeSelf().Count());
}
Update: If you really just want a Position method, just add an extension method.
public static class ExMethods
{
public static int Position(this XNode node)
{
return node.NodesBeforeSelf().Count();
}
}
Now you can just call x.Position(). :)
Actually in the Load method of XDocument you can set a load option of SetLineInfo, you can then typecast XElements to IXMLLineInfo to get the line number.
you could do something like
var list = from xe in xmldoc.Descendants("SomeElem")
let info = (IXmlLineInfo)xe
select new
{
LineNum = info.LineNumber,
Element = xe
}
static int Position(this XNode node) {
var position = 0;
foreach(var n in node.Parent.Nodes()) {
if(n == node) {
return position;
}
position++;
}
return -1;
}

Resources