XPathSelectElement catch null reference exception - c#

I am using XPathSelectElement to get some elements from an XML file. It works when those elements are present in the XML file. If the selected element is NOT in the XML it naturally throws a "Null Reference" exception. Which is fine. I would expect it to do that. However, I want to be able to catch that exception and do nothing if the XPathSelectElement is null.
Code that works as expected:
public void LoadBonusDescription()
{
string Race = CharRaceSelector.Text;
string bonus = RequirementsBox.Text;
XDocument doc = XDocument.Load($"{Gamepath}");
string description = (string)doc.XPathSelectElement($"//RaceID[#id='{Race}']/Bonus[#id='{bonus}']/Description").Value;
//DescriptionBox is a listBox
DescriptionBox.Text = description;
}
I tried throwing in an if statement like:
if (description == null)
{
return;
}
else
{
DescriptionBox.Text = description;
}
But it doesn't hit that part, and throws the exception at the string variable assignment here:
string description = (string)doc.XPathSelectElement($"//RaceID[#id='{Race}']/Bonus[#id='{bonus}']/Description").Value;
How do I catch the exception BEFORE (or during) the variable assignment in order to run the if statement?
If I can't catch it, is there a way to disable the DescriptionBox listBox AND NOT turn the text in the box to gray (as it does with DescriptionBox.Enabled = false;)?
Basically I want to prevent users from selecting items that aren't available in the XML file.

Added:
var description = (string)doc.XPathSelectElement($"//RaceID[#id='{Race}']/Bonus[#id='{bonus}']/Description");
if (description == null)
{
return;
}
else
{
DescriptionBox.Text = description;
}
as #dbc suggested and it works perfectly!

Related

Read Text File, Update Fields C# and WPF

I am trying to basically create config files. A text file will hold something like:
Name::Adam
Location::Washington
I am trying to grab the first part as the field name (i.e. Name.Text would update the TextBox) then put the second part to that Text. Just not sure where to go or what the best way to build this is. The code below is incomplete because I can't figure out how to update the textboxes.
Thanks for the help!
private void clickImportConfig_ItemClick(object sender, DevExpress.Xpf.Bars.ItemClickEventArgs e)
{
Stream myStream = null;
string fieldUpdate = string.Empty;
string fieldUpdateTo = string.Empty;
try
{
using (myStream)
{
string[] lines = File.ReadAllLines(#"c:\\config.txt");
foreach (string s in lines)
{
var splitted = Regex.Split(s, "::");
fieldUpdate = splitted[0].ToString();
fieldUpdateTo = splitted[1].ToString();
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
}
}
I think this is what you're looking for:
private void clickImportConfig_ItemClick(object sender, DevExpress.Xpf.Bars.ItemClickEventArgs e)
{
Stream myStream = null;
string fieldUpdate = string.Empty;
string fieldUpdateTo = string.Empty;
try
{
using (myStream)
{
string[] lines = File.ReadAllLines(#"c:\\config.txt");
foreach (string s in lines)
{
string[] splitted = s.Split(new string[] { "::" }, StringSplitOptions.RemoveEmptyEntries);
fieldUpdate = splitted[0].ToString();
fieldUpdateTo = splitted[1].ToString();
// TextBox textBox = (TextBox)this.FindName(fieldUpdate);
// Or
TextBox textBox = this.FindName(fieldUpdate) as TextBox;
// See below for an explanation
if (textBox != null) // FindName returns null if nothing is found with that name
{
textBox.Text = fieldUpdateTo;
}
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
}
}
As insane_developer pointed out, you will be better off using the String.Split method (s being the string in this case so written as s.Split()) instead of Regex.Split. It will give you the benefit of removing any empty results from the array. It may also have better performance as Regex is capable of a lot more complicated things, but I haven't tested that so I could be wrong.
You can use the FindName(string name) method to find an element with the specified name. This method returns null if nothing is found and an object if the element is found. This object will need to be cast to the type you are expecting (I.e. TextBox). You can do this in one of the following ways:
TextBox textBox = (TextBox)this.FindName(fieldUpdate);
or
TextBox textBox = this.FindName(fieldUpdate) as TextBox;
The first option will throw an InvalidCastException if FindName returns an object which is not a TextBox. The second option will instead just set the value of textBox to null which will be checked by the if statement and the exception will be avoided. As you are only catching all generic exceptions in this code, an InvalidCastException would show your "Could not read file from disk" message which is not true. So you may want to add an additional catch block to handle any invalid casting.
If you're wondering why you don't just stick to the second option as it solves this problem, then consider this scenario as an example. Lets say in the future you decide for some reason that you want to change all of your TextBox to TextBlock or something else, but forget to come back to change this code, or accidently end up with the name of another type of control in your text file. The second option will set the value of textBox to null and your field(s) won't be updated. But there will be absolutely no errors, leaving you scratching your head and having to debug the problem. The first option would throw an InvalidCastException showing you exactly where the problem is. You could then choose how to handle this problem by either showing another message box or silently writing the error to a log file etc.
You don't need a regular expression, just:
var splitted = s.Split("::", StringSplitOptions.RemoveEmptyEntries);
fieldUpdate = splitted[0];
fieldUpdateTo = splitted[1];
For the rest you have to be more explicit

Check if an xml section exist in a file using XDocument

I have some code that reads in an xml file. However it is triggering an error at the 3rd IF statement:
if (xdoc.Root.Descendants("HOST").Descendants("Default")
.FirstOrDefault().Descendants("HostID")
.FirstOrDefault().Descendants("Deployment").Any())
Error:
System.NullReferenceException: Object reference not set to an instance of an object.
That is because in this particular file there is no [HOST] section.
I was assuming that on the first IF statement, if it didn't find any [HOST]section it would not go into the statement and therefore i should not get this error. Is there a way to check if a section exists first?
XDocument xdoc = XDocument.Load(myXmlFile);
if (xdoc.Root.Descendants("HOST").Any())
{
if (xdoc.Root.Descendants("HOST").Descendants("Default").Any())
{
if (xdoc.Root.Descendants("HOST").Descendants("Default").FirstOrDefault().Descendants("HostID").FirstOrDefault().Descendants("Deployment").Any())
{
if (xdoc.Root.Descendants("HOST").Descendants("Default").FirstOrDefault().Descendants("HostID").Any())
{
var hopsTempplateDeployment = xdoc.Root.Descendants("HOST").Descendants("Default").FirstOrDefault().Descendants("HostID").FirstOrDefault().Descendants("Deployment").FirstOrDefault();
deploymentKind = hopsTempplateDeployment.Attribute("DeploymentKind");
host = hopsTempplateDeployment.Attribute("HostName");
}
}
}
}
Within the body of this if block...
if (xdoc.Root.Descendants("HOST").Descendants("Default").Any())
{
if (xdoc.Root.Descendants("HOST").Descendants("Default").FirstOrDefault().Descendants("HostID").FirstOrDefault().Descendants("Deployment").Any())
{
if (xdoc.Root.Descendants("HOST").Descendants("Default").FirstOrDefault().Descendants("HostID").Any())
{
var hopsTempplateDeployment = xdoc.Root.Descendants("HOST").Descendants("Default").FirstOrDefault().Descendants("HostID").FirstOrDefault().Descendants("Deployment").FirstOrDefault();
deploymentKind = hopsTempplateDeployment.Attribute("DeploymentKind");
host = hopsTempplateDeployment.Attribute("HostName");
}
}
}
...you have established that the element <Root>/HOST/Default exists. You however don't know whether <Root>/HOST/Default/HostId/Deployment exists. If it doesn't you will get a NullReferenceException like the one you're experiencing due to the use of FirstOrDefault. It is generally recommended to use First in cases where you expect the elements to be present, which will give you at least a better error message.
If you expect the elements to be not present, a simple solution is to use the ?. along the respective LINQ2XML axis:
var hopsTemplateDeployment =
xdoc.Root.Descendants("HOST").Descendants("Default").FirstOrDefault()
?.Descendants("HostID").FirstOrDefault()
?.Descendants("Deployment").FirstOrDefault();
if (hopsTemplateDeployment != null)
{
deploymentKind = hopsTemplateDeployment.Attribute("DeploymentKind");
host = hopsTemplateDeployment.Attribute("HostName");
}
It will also save you the chain of nested if clauses.

StackOverflow in SelectSingleNode

Hello I have function which creates/updates fields in app.exe.config file
public static void UpdateConfig(string FieldName, string FieldValue, ConfigSelector SectionName = ConfigSelector.AppSettings)
{
switch (SectionName)
{
case ConfigSelector.Execption:
{
// MessageBox.Show("gg");
var xmlDoc = new XmlDocument();
xmlDoc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
if (xmlDoc.SelectSingleNode("configuration/Execption") != null)
{
if (xmlDoc.SelectSingleNode("configuration/Execption/List") != null)
{
// create new node <add key="Region" value="Canterbury" />
var nodeRegion = xmlDoc.CreateElement("add");
nodeRegion.SetAttribute("key", FieldName);
nodeRegion.SetAttribute("value", FieldValue);
xmlDoc.SelectSingleNode("configuration/Execption/List").AppendChild(nodeRegion);
}
else
{
var List = xmlDoc.CreateElement("List");
xmlDoc.SelectSingleNode("configuration/Execption").AppendChild(List);
UpdateConfig(FieldName, FieldValue, SectionName);
}
}
else
{
var List = xmlDoc.CreateElement("Execption");
xmlDoc.SelectSingleNode("configuration").AppendChild(List);
UpdateConfig(FieldName, FieldValue, SectionName);
}
xmlDoc.Save(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
ConfigurationManager.RefreshSection("Execption/List");
break;
}
}
}
Function works first Check if xpath configuration/Execption exist, if not exist it creates this path and recalls function again, second time check if configuration/Execption/List path exist if not creates path and recalls function again, and third time adds required fields which is fieldname and fieldvalue,
but I getting System.StackOverflowException in line:
if (xmlDoc.SelectSingleNode("configuration/Execption") != null)
Did I miss something?
You are calling UpdateConfig recursively, with the exact same arguments already passed to it
UpdateConfig(FieldName, FieldValue, SectionName);
Since the recursive call happens before the xmlDoc.Save(), it always works on the same content.
Saving before doing the recursive call should fix the issue.
You don't save the document after adding the new element, so when you are loading the file in the next iteration the new element isn't there, and xmlDoc.SelectSingleNode("configuration/Execption") != null is still false, so the code creates the element again in infinite recursion and you get StackOverflowException.
Just save the document after you change it
else
{
var List = xmlDoc.CreateElement("Execption");
xmlDoc.SelectSingleNode("configuration").AppendChild(List);
xmlDoc.Save(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
UpdateConfig(FieldName, FieldValue, SectionName);
}

Getting user settings collection types' default values in Visual Studio .NET

I want the ability to reset a single user setting easily to its default value.
I've written an extension like so:
public static void sReset(this Properties.Settings set, string key = "")
{
if (key == "")
{
set.Reset();
return;
}
Console.WriteLine("Reset '" + key + "' to: " + set.Properties[key].DefaultValue);
set.PropertyValues[key].PropertyValue = set.PropertyValues[key].Property.DefaultValue;
}
And this works fine for primitive types.
But now I want to apply that to a stringCollection, and it fails:
An unhandled exception of type 'System.InvalidCastException' occurred
in myApp.exe
Additional information: Unable to cast object of type 'System.String'
to type 'System.Collections.Specialized.StringCollection'.
This is because these collection type default values (Stored serialized as XML) are returned as strings:
set.Properties[key].DefaultValue.GetType() returns System.String
I can see in the settings designer that usually, it just casts the value to StringCollection:
public global::System.Collections.Specialized.StringCollection settingsName {
get {
return ((global::System.Collections.Specialized.StringCollection)(this["settingsName"]));
}
set {
this["settingsName"] = value;
}
}
But that fails after assigning the DefaultValue, with the above error message.
I tried casting the XML String before assigning, but of course that failed there, too.
How is an XML String like this converted before being assigned to the settings property?
What do I need to do to make this work?
Nobody?
That sucks, I would've thought there was a more elegant solution...
So I'm using this now:
Check if the Type of the setting is StringCollection
Read out XML Elements at "ArrayOfString/string"
... <-- wouldn't let me format code as code without an additional paragraph here. What the hell?
public static void sReset(this Properties.Settings set, string key = "")
{
if (key == "")
{
set.Reset();
return;
}
if (set.PropertyValues[key].Property.PropertyType == typeof(StringCollection))
{
string tvs = (string)set.PropertyValues[key].Property.DefaultValue;
set.PropertyValues[key].PropertyValue = tvs.XmlToStringCollection();
return;
}
set.PropertyValues[key].PropertyValue = set.PropertyValues[key].Property.DefaultValue;
}
public static StringCollection XmlToStringCollection(this string str)
{
StringCollection ret = new StringCollection();
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(str);
XmlNodeList terms = xmlDoc.SelectNodes("ArrayOfString/string");
foreach (XmlElement s in terms)
{
if (s.InnerXml != "")
{
Console.WriteLine("Found: " + s.InnerXml);
ret.Add(s.InnerXml);
}
}
return ret;
}
since the XML looks like this:
<?xml version="1.0" encoding="utf-16"?>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<string>first string</string>
<string>second string</string>
<string>...</string>
</ArrayOfString>
...Which is a shame, it's damn ugly and limited:
You'd have to do the same for any kind of XML serialized type
Really, what I'd like is a way to get the default value the same way VS does.
I can't imagine it could be as hacky as this, could it?
I realize this is an old question, but if anyone else lands here like I did, the OP's solution works. However, to answer OP's final question, I suspect Microsoft is using the XmlSerializer class, something like this extension method I came up with, though I also couldn't find a simple method call to do it for me:
public static StringCollection ToStringCollection(this string str)
{
// Create an instance of the XmlSerializer.
XmlSerializer serializer =
new XmlSerializer(typeof(StringCollection));
// Declare an object variable of the type to be deserialized.
StringCollection sc;
using (Stream reader = new MemoryStream(Encoding.Unicode.GetBytes(str)))
{
// Call the Deserialize method to restore the object's state.
sc = (StringCollection)serializer.Deserialize(reader);
}
return sc;
}
The above is based upon example code from https://learn.microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlserializer.deserialize?view=net-6.0

code contracts usage with exceptions

void ReadContent(string path)
{
Contract.Requires(path!=null);
string contentofileasstring = filehelperobj.GetContent(path);
if(String.IsNullOrEmpty(contentofileasstring ))
{
throw new FileContentException(path + "No content found");
}
m_xmlobj = contentofileasstring ;
}
Is my assumption of the usage of code contracts and exceptions right in this case. Do you think it is logical to replace the exception with a code contract(or vice versa)?
code not tested.Just an example scenario
I would probably go for an implementation which looks like the following:
private void ReadContent(string path)
{
Contract.Requires<FileMissingException>(File.Exists(path));
string content = filehelperobj.GetContent(path);
m_xmlobj = content;
}
Post Edit
As it's the content you want to validate, I would put a Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); inside the filehelperobj.GetContent(string) method. Then if the content being read was null or empty, I would throw an exception. e.g.
public string GetContent(string path)
{
Contract.Requires<FileMissingException>(File.Exists(path));
Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>()));
using(var reader = new StreamReader(File.OpenRead(path)))
{
var content = reader.ReadToEnd();
if(String.IsNullOrEmpty(content))
throw new FileContentException("No content found at file: " + path);
return content;
}
}
Well assuming you had the lines the wrong way round (ie, test the path for null before trying to use it) then yes, it is a valid pre-condition and therefore should be a code contract.

Categories