LINQ to XML "System.Exception: Value cannot be null" - c#

I am working with LINQ to XML in C#. I have the following code, the last line keeps throwing a System.Exception: Value cannot be null. I can't figure out what the problem is. I've tried everything.
AuthorizedToSign is a List. I was able to perform the same action using a bulky nested foreach loop.
I know for sure there are no errors as far as the XML file itself.
If anyone can help I would greatly appreciate it.
BusinessAccounts = (from a in accountRoot.Elements()
where (bool)a.Element("Business") == true
select new BusinessAccount()
{
OpenDate = (DateTime)a.Element("DateOpened"),
Password = a.Element("Password").Value,
Balance = (double)a.Element("Balance"),
AccountStatus = (Status)Enum.Parse(typeof(Status), a.Element("Status").Value),
//Element AuthToSign has a collection of sub-elements called "authName"
//couldn't get the code below to work
AuthorizedToSign = (from el in a.Element("AuthorizedToSign").Elements()
select el.Element("AuthName").Value).ToList()
}).ToList();
changing select el.Element("AuthName").Value)
to select (string)el.Element("AuthName"))
doesn't help.
The XML file has a lot of entries that look like this:
<?xml version="1.0" encoding="utf-8"?>
<Accounts>
<BusinessAccount>
<Business>true</Business>
<AccountNumber>34534456</AccountNumber>
<AccountBranchID>100</AccountBranchID>
<AccountName>Elgris Tech</AccountName>
<CompanyName>Elgris Tech</CompanyName>
<CompanyID>235</CompanyID>
<CreditLimit>50000</CreditLimit>
<DateOpened>2014-12-13T00:00:00</DateOpened>
<Balance>1200</Balance>
<Password>1234</Password>
<Status>Active</Status>
<AuthorizedToSign>
<AuthName>Yechiel</AuthName>
<AuthName>Lev</AuthName>
<AuthName>Roman</AuthName>
</AuthorizedToSign>
</BusinessAccount>
<PrivateAccount>
<Business>false</Business>
<AccountNumber>34534458</AccountNumber>
<AccountBranchID>100</AccountBranchID>
<AccountName>Yechiel L.</AccountName>
<CustomerName>Yechiel L.</CustomerName>
<CustomerAddress>2sadfasosa, CA</CustomerAddress>
<CustomerPhone>8-4268</CustomerPhone>
<CardNumber>304456</CardNumber>
<CreditLimit>10000</CreditLimit>
<DateOpened>1994-06-23T00:00:00</DateOpened>
<Balance>555000</Balance>
<Password>pass</Password>
<Status>Active</Status>
</PrivateAccount>
</Accounts>

UPDATE
According to your xml, PrivateAccount element does not have AuthorizedToSign node, thus referencing to AuthorizedToSign node throws an exception. So, in your case solution will be simple:
from a in accountRoot.Elements()
let authorized = a.Element("AuthorizedToSign")
where (bool)a.Element("Business")
select new BusinessAccount()
{
OpenDate = (DateTime)a.Element("DateOpened"),
Password = (string)a.Element("Password"),
Balance = (double)a.Element("Balance"),
AccountStatus = (Status)Enum.Parse(typeof(Status), (string)a.Element("Status")),
AuthorizedToSign = authorized == null ? null : // or new List<string>()
authorized.Elements()
.Select(auth => (string)auth)
.ToList()
};
Getting auth names with query syntax:
AuthorizedToSign = authorized == null ? null : // or new List<string>()
(from auth in authorized.Elements()
select (string)auth).ToList()

Related

XML Attribute Values C#

I am trying to retrieve the value of two XML attributes and having a hard time using Xelement.
Basically I am getting an API response that has uses the same attribute naming convention for two different values.
This is the response I am getting back after making the call.
-<ResponseData>
<Type value="7" id="Level"/>
<Type value="67.80" id="Score"/>
</ResponseData>
I want to set the two values to two different variables. level & score. I have the fulling code however I keep getting back a null reference error.
var xmlResponse = XElement.Parse(response);
var level = "";
var score = "";
if(xmlResponse.Attribute("id").Value == "Level")
{
level = (string) xmlResponse.Attribute("value");
}
if(xmlResponse.Attribute("id").Value == "Score")
{
score = (string) xmlResponse.Attribute("value");
}
I want my two variables to be set to the values I get back from the API call.
Any help is appreciated
Side note, if this is a SOAP service with a WSDL, have you tried letting Visual Studio scaffold it?
So you have to think about it this way.. your now parsed XElement is a ResponseData object, which has a collection of Type objects in it now, and each Type object has a value and id attribute, which means you need to find the right Type entry for each value you're looking for. EG:
string data = #"<ResponseData>
<Type value=""7"" id=""Level""/>
<Type value=""67.80"" id=""Score""/>
</ResponseData>";
var xmlResponse = XElement.Parse(data);
var levelElement = xmlResponse.Elements("Type")?
.FirstOrDefault(x => x.Attribute("id")?.Value == "Level");
var scoreElement = xmlResponse.Elements("Type")?
.FirstOrDefault(x => x.Attribute("id")?.Value == "Score");
int level;
double score;
level = int.Parse(levelElement?.Attribute("value")?.Value ?? "-1");
score = double.Parse(scoreElement.Attribute("value")?.Value ?? "-1");
Console.WriteLine($"Score {score}; Level {level}");
Why not use?
-<ResponseData>
<Type Level="7" Score="67.80"/>
</ResponseData>
I would recommend using XML that powershell knows how to work with.
[xml]$xml = Get-Content C:\temp\xml.txt
$TypeNode = $xml.SelectSingleNode("//Type[#id='Score']")
$TypeNode.Attributes["value"].Value

wrong value in result query so Null Reference Exception in Linq query

I have two xml files which I am comparing with each other. The Linq query result1 is throwing Null Reference Exception after executing correctly for one rule. And when I debugged I found the section is displaying wrong values. I am unable to figure out the cause.
Rules.xml file:
<rule id="1" numberofsections="2">
<section id="1" attributeid="1686" ruleoperator="==" condition="and">
<name>Processor type</name>
<value>Core i3</value>
</section>
<section id="2" attributeid="1438" ruleoperator="<" condition="and" >
<name>Weight</name>
<value>3.8 LBS</value>
</section>
<type>ultrabook</type>
</rule>
And the code snippet:
XDocument rulesXml = XDocument.Load("/RulesEnginescope/RulesEnginescope/rulesSubType.xml");
XDocument productXml = XDocument.Load("c:/RuleEngine/RuleEngine/product.xml");
var getSelectedLeafCategoryRules = from rules2 in rulesXml.Descendants("QueryTransformation").Descendants("leafcategory")
where ((long)System.Convert.ToDouble(rules2.FirstAttribute.Value) == 4590)
select rules2;
var rules = getSelectedLeafCategoryRules.Descendants("rule");
var productAttribute = productXml.Descendants("AttrList").Descendants("Attr");
foreach (var x in rules)
{
var section = x.Elements("section");
/*Wrong value in section.count()*/
Console.WriteLine(section.Count());
var result1 = from p in section
from pa in productAttribute
where (p.Attribute("attributeid").Value == pa.Attribute("id").Value
&& p.Element("name").Value == pa.Element("Name").Value)
select new
{
ruleAttribute = new
{
ruleId = p.Attribute("attributeid").Value,
ruleOperator = p.Attribute("ruleoperator").Value,
name = p.Element("name").Value,
value = p.Element("value").Value,
condition = p.Attribute("condition").Value
},
prodAttribute = new
{
productId = pa.Attribute("id").Value,
name = pa.Element("Name").Value,
value = pa.Element("ValueList").Element("Value").Value
/*Error*/ }
};
if (result1.Count() != 0 && result1.Count() == System.Convert.ToInt64(x.Attribute("numberofsections").Value))
{
//checking each section
foreach (var r in result1)
{
...
}
}
The idiomatic way to get the value of elements and attributes in LINQ-to-XML is to cast the element or attribute to the type you want, rather than accessing the Value attribute.
prodAttribute = new
{
productId = (string)pa.Attribute("id"),
name = (string)pa.Element("Name"),
// ...
}
Using this pattern avoids null ref exceptions caused when calls to Attribute() and Element() don't find a matching node. It also reduces verbosity:
((long)System.Convert.ToDouble(rules2.FirstAttribute.Value)
// should be
(long)rules2.FirstAttribute
You'll still need to add null checks when you're accessing children of children. This can get verbose; one way to keep it succinct is to use IEnumerable-oriented methods so that you're operating on a (possibly empty) collection, rather than a (possibly null) instance.
pa.Element("ValueList").Element("Value").Value
// could be
(string)pa.Elements("ValueList").Elements("Value").FirstOrDefault ()
Finally, note that capitalization matters in LINQ-to-XML. In your code you seem to be switching capitalization patterns ("id" vs. "Name") often; it's likely that your source XML is more consistent.

How to write XPath expression to select node name from its value

I'm trying to write an XPath expression to select the name of a node from its value in "qualities" and then select in "qualityNames" the value inside node whose name has previously captured.
E.g. In "qualities" - got value "4", take name "rarity3" then in "qualityNames" I got node named "rarity3" and take value "amazingrarity"
<result>
<status>1</status>
<qualities>
<Normal>0</Normal>
<rarity1>1</rarity1>
<rarity2>2</rarity2>
<vintage>3</vintage>
<rarity3>4</rarity3>
<rarity4>5</rarity4>
</qualities>
<qualityNames>
<Normal>Normal</Normal>
<rarity1>Genuine</rarity1>
<rarity2>rarity2</rarity2>
<vintage>Vintage</vintage>
<rarity3>amazingrarity</rarity3>
<rarity4>Unusual</rarity4>
</qualityNames>
</result>
I'm doing this in C# (It's a MVC App) and I'd prefer to use XPath because I'm indexing the XML and I haven't found a fastest way to query in-memory technique (this XML file has ~3MB and I'm using IndexingXPathNavigator).
Use the local-name() and text() functions + predicates. For value "4" it will be
//qualityNames/*[local-name()=local-name(//qualities/*[text() = '4'])]
Tested with http://www.xpathtester.com
Sounds like you want to create a dictionary of key/value pairs (assuming the node names are only needed to find matches and aren't important to your code).
If so, you can use the following:
var doc = XElement.Parse(#"<result>
<status>1</status>
<qualities>
<Normal>0</Normal>
<rarity1>1</rarity1>
<rarity2>2</rarity2>
<vintage>3</vintage>
<rarity3>4</rarity3>
<rarity4>5</rarity4>
</qualities>
<qualityNames>
<Normal>Normal</Normal>
<rarity1>Genuine</rarity1>
<rarity2>rarity2</rarity2>
<vintage>Vintage</vintage>
<rarity3>amazingrarity</rarity3>
<rarity4>Unusual</rarity4>
</qualityNames>
</result>");
var query = from quality in doc.XPathSelectElements("qualities/*")
join qualityName in doc.XPathSelectElements("qualityNames/*")
on quality.Name equals qualityName.Name
select new { Key = quality.Value, Value = qualityName.Value };
var qualities = query.ToDictionary(a => a.Key, a => a.Value);
var quality3 = qualities["3"];
// quality3 == "Vintage"
var quality4 = qualities["4"];
// quality4 == "amazingrarity"
EDIT: example of how to cache this dictionary
// add reference to System.Web dll
public Dictionary<string, string> GetQualities()
{
// assuming this code is in a controller
var qualities = this.HttpContext.Cache["qualities"] as Dictionary<string, string>;
if (qualities == null)
{
// LoadQualitiesFromXml() is the code above
qualities = LoadQualitiesFromXml();
this.HttpContext.Cache["qualities"] = qualities;
}
return qualities;
}
I think this is what you asked
var rarity3ValueInQualities = xml.SelectSingleNode("/result/qualities/rarity3").InnerText;
var rarity3ValueInqualityNames = xml.SelectSingleNode("/result/qualityNames/rarity3").InnerText;

parsing xml in c# using LINQ

I have the following xml structure
<userlist>
<user Name="something">
<function name="funcname">
<picture name="pictname">
<curve name="curvename">
<name>NAME</name>
...
</curve>
</picture>
</function>
<function name="function2">
...
</function>
</user>
It goes on a bit more. I have written a function to extract of the "function" tags and place them in objects using code that simplifies to this:
from function in xmlDoc.Descendants("function")
select new FUNCTIONOBJECT {
do all the rest...
}.toList<FUNCTIONOBJECT>();
I am now trying to make it so that I only filter the functions for a given user. so the name attribute of the user is given. Can anyone tell me how I can make this work with LINQ?
My attempt was:
from user in xmlDoc.Descendants("user")
where user.Attribute("Name").Value == givenusername
select {
var functions =
from function in user.Descendants("function")
select new FUNCTIONOBJECT {
... more stuff
}.toList<FUNCTIONOBJECT>();
But this is wrong and doesnt work.
All help is good. I am pretty new to c# and still trying to wrap my head around xml parsing with LINQ.
EDIT:
updated version of what I have and still doesnt work:
XDocument xmlDoc = XDocument.Load(path);
var functionlist =
(from user in xmlDoc.Descendants("user")
where user.Attribute("Name").Value == username
select(
(from function in user.Descendants("function")
select new Function
{
name = function.Attribute("name").Value,
pictures =
(from picture in function.Descendants("picture")
select new Picture
{
name = picture.Attribute("name").Value,
layout = picture.Element("layout").Value,
curves =
(from curve in picture.Descendants("curve")
select new Curve
{
name = curve.Attribute("name").Value,
section = curve.Element("section").Value,
run = curve.Element("run").Value,
folder = curve.Element("folder").Value,
drivingpoint = curve.Element("drivingpoint").Value,
display = int.Parse(curve.Element("display").Value),
points =
(from point in curve.Descendants("point")
select new Point
{
id = point.Element("id").Value != null ? point.Element("id").Value : string.Empty,
direction = point.Element("direction").Value != null ? point.Element("direction").Value : string.Empty,
}).ToList<Point>(),
}).ToList<Curve>(),
}).ToList<Picture>(),
}).ToList<Function>(),
).toList();
}
Just few syntax mistakes. Otherwise, the content was correct. Its a bit tricky to learn C# and LINQ syntax at the same time (a language in a language). Here is the corrected code:
from user in xmlDoc.Descendants("user")
where user.Attribute("Name").Value == givenusername
select ((from function in user.Descendants("function") // When you do a "select something" "something" must have a value, so you can't begin with "{ var functions = ..."
select new FUNCTIONOBJECT
{
// more stuff
}).ToList(); // You don't have to specify <FUNCTIONOBJECT> because the compiler deduce it from the context (here there a new FUNCTIONOBJECT
But here, you will have a List<List<FUNCTIONOBJECT>>. Why? Because there is no information in the code that specify that only 1 user has the givenusername.
If it is the case, just split the code:
// Gets the user
var user = (from user in xmlDoc.Descendants("user")
where user.Attribute("Name").Value == givenusername
select user).Single(); // Get the only user that satisfy the condition (Throw an exception if no user has the given name or if multiple users have the given name)
// Gets its functions
List<FUNCTIONOBJECT> functions = (from function in user.Descendants("function")
select new FUNCTIONOBJECT
{
// more stuff
}).ToList();

Why this linq query returns only first attribute?

I have a complicated xml file and in different levels the following part may exist:
<ChrNote>
<note>The appropriate character is:</note>
</ChrNote>
<ChrDef>
<extension char="A">initial</extension>
<extension char="D">subsequent</extension>
<extension char="G">subsequent delayed</extension>
<extension char="S">sequel </extension>
</ChrDef>
...
and here is the code snippet that I use to build a text file:
var lv1s = from lv1 in XMLDoc.Descendants("chapter").DescendantsAndSelf()
select new
{
SChar = (string)lv1.Element("ChrNote") ?? "",
SCharDef = (string)lv1.Element("ChrDef") ?? "",
//Returns only first attribute ..?
CharLetter = (lv1.Element("ChrDef") == null ? "" :
(string)(lv1.Element("ChrDef")
.Element("extension")
.Attribute("char")) ?? "")
};
The problem is that the above query returns only the first attribute ("A") from element "extension". I have no experience in linq and any help will be appreciated.
(I know that your question says "returns only the first attribute", but there is only one attribute there -- so I'm answering your implied question instead.)
It's only returning the first extension element because you're using Element("extension"). If you used Elements("extension") instead, you'd get the others.
See the documentation:
http://msdn.microsoft.com/en-us/library/system.xml.linq.xcontainer.element.aspx
http://msdn.microsoft.com/en-us/library/bb348975.aspx
Is this what you are looking for?
var result = from x in XMLDoc.Descendants("chapter")
let Definitions = x.XPathSelectElements("ChrDef/extension")
select new
{
Note = x.XPathSelectElement("ChrNote/note") == null ? "" : x.XPathSelectElement("ChrNote/note").Value,
Definitions = Definitions.Select(y=> new { Extension = y.Value, Char = y.Attribute("char").Value })
};
Console.WriteLine ("{0}", result.First().Note);
foreach (var definition in result.First().Definitions)
{
Console.WriteLine ("{0}, {1}", definition.Extension, definition.Char);
}
This will give you the following output:
The appropriate character is:
initial, A
subsequent, D
subsequent delayed, G
sequel , S

Categories