Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / XML
Tip/Trick

Using Extension Methods To Avoid XML Problems

Rate me:
Please Sign up or sign in to vote.
4.80/5 (18 votes)
10 Jul 2014CPOL1 min read 48.5K   15   25
Extension methods can help you avoid sticky situations.

In my current Silverlight3 project at work, I'm retrieving a string of XML data from a database via stored procedure. An object is initialized with this XML data via an Element property (of type XElement), and the following code is executed:
 

public XElement Element
{
    set
    {
        this.MyVar1 = value.Element("MyVar1").Value;
        // this element is missing - exception!!!
        this.MyVar2 = value.Element("MyVar2").Value;
    }
}

 
I discovered that when a database column contains NULL as its value, and the stored procedure will not return the desired/expected XML child element for the row, the code shown above will throw an exception. To combat this, I've written the following extension methods. These methods allow me to "adjust" the data on the fly so that:
 
a) There is always valid data in my object
 
b) Avoids the inevitable exception in the event that an element is missing.
 
Here's the code:
 

public static class ExtensionMethods
{
    //--------------------------------------------------------------------------------
    public static bool Contains(this XElement root, string name)
    {
	bool contains = false;
	foreach (XElement element in root.Elements())
	{
            if (element.Name == name)
	    {
		contains = true;
		break;
	    }
	}
        return contains;
    }
 
    //--------------------------------------------------------------------------------
    public static string GetValue(this XElement root, string name, string defaultValue)
    {
        string value = defaultValue;
        if (root.Contains(name))
        {
            value = root.Element(name).Value;
        }
        return value;
    }
}

 
Now, I can use this code to set the property, and I can stop worrying about the possibility of missing columns:
 

public XElement Element
{
    set
    {
        this.MyVar1 = value.GetValue("MyVar1", "NO VALUE");
        // still missing, but that's okay!
        this.MyVar2 = value.GetValue("MyVar2", "NOVAL");
    }
}

 
Using extension methods allowed me to add functionality and avoid exceptions without adding complexity to the programmer-facing code. Very cool.
 
EDIT ----------
 
Fixed some spelling errors, and reworded the descriptive text a bit.
 
EDIT (01/21/2011) ===============
 
I decided I should probably post ALL of the methods I use. Since I originally posted this tip, I've added overloads for GetValue, and added GetAttribute (and its overloads). The overloads were implemented to avoid the casting that was necessary where the method was being called. The ones that are shown below fill my own requirements, and you can, of course, add more as your needs evolve:
 

C#
//--------------------------------------------------------------------------------
public static string GetValue(this XElement root, string name, string defaultValue)
{
    return (string)root.Elements(name).FirstOrDefault() ?? defaultValue;
}
 
//--------------------------------------------------------------------------------
public static double GetValue(this XElement root, string name, double defaultValue)
{
    string strValue = (string)root.Elements(name).FirstOrDefault() ?? defaultValue.ToString();
    double value;
    if (!double.TryParse(strValue, out value))
    {
        throw new Exception(string.Format("Element {0}: Value retrieved was not a valid double", name));
    }
    return value;
}
 
//--------------------------------------------------------------------------------
public static decimal GetValue(this XElement root, string name, decimal defaultValue)
{
    string strValue = (string)root.Elements(name).FirstOrDefault() ?? defaultValue.ToString();
    decimal value;
    if (!decimal.TryParse(strValue, out value))
    {
        throw new Exception(string.Format("Element {0}: Value retrieved was not a valid decimal", name));
    }
    return value;
}
 
//--------------------------------------------------------------------------------
public static Int32 GetValue(this XElement root, string name, Int32 defaultValue)
{
    string strValue = (string)root.Elements(name).FirstOrDefault() ?? defaultValue.ToString();
    Int32 value;
    if (!Int32.TryParse(strValue, out value))
    {
        throw new Exception(string.Format("Element {0}: Value retrieved was not a valid 32-bit integer", name));
    }
    return value;
}
 
//--------------------------------------------------------------------------------
public static bool GetValue(this XElement root, string name, bool defaultValue)
{
    string strValue = (string)root.Elements(name).FirstOrDefault() ?? defaultValue.ToString();
    bool value;
    if (!bool.TryParse(strValue, out value))
    {
        throw new Exception(string.Format("Element {0}: Value retrieved was not a valid boolean", name));
    }
    return value;
}
 
//--------------------------------------------------------------------------------
public static DateTime GetValue(this XElement root, string name, DateTime defaultValue)
{
    string strValue = (string)root.Elements(name).FirstOrDefault() ?? defaultValue.ToString();
    DateTime value;
    if (!DateTime.TryParse(strValue, out value))
    {
        throw new Exception(string.Format("Element {0}: Value retrieved was not a valid DateTime", name));
    }
    return value;
}
 
//--------------------------------------------------------------------------------
public static string GetAttribute(this XElement root, string name, string defaultValue)
{
    return (string)root.Attributes(name).FirstOrDefault() ?? defaultValue;
}
 
//--------------------------------------------------------------------------------
public static double GetAttribute(this XElement root, string name, double defaultValue)
{
    string strValue = (string)root.Attributes(name).FirstOrDefault() ?? defaultValue.ToString();
    double value;
    if (!double.TryParse(strValue, out value))
    {
        throw new Exception(string.Format("Attribute {0}: Value retrieved was not a valid double", name));
    }
    return value;
}
 
//--------------------------------------------------------------------------------
public static decimal GetAttribute(this XElement root, string name, decimal defaultValue)
{
    string strValue = (string)root.Attributes(name).FirstOrDefault() ?? defaultValue.ToString();
    decimal value;
    if (!decimal.TryParse(strValue, out value))
    {
        throw new Exception(string.Format("Attribute {0}: Value retrieved was not a valid decimal", name));
    }
    return value;
}
 
//--------------------------------------------------------------------------------
public static Int32 GetAttribute(this XElement root, string name, Int32 defaultValue)
{
    string strValue = (string)root.Attributes(name).FirstOrDefault() ?? defaultValue.ToString();
    Int32 value;
    if (!Int32.TryParse(strValue, out value))
    {
        throw new Exception(string.Format("Attribute {0}: Value retrieved was not a valid 32-bit integer", name));
    }
    return value;
}
 
//--------------------------------------------------------------------------------
public static bool GetAttribute(this XElement root, string name, bool defaultValue)
{
    string strValue = (string)root.Attributes(name).FirstOrDefault() ?? defaultValue.ToString();
    bool value;
    if (!bool.TryParse(strValue, out value))
    {
        throw new Exception(string.Format("Attribute {0}: Value retrieved was not a valid boolean", name));
    }
    return value;
}
 
//--------------------------------------------------------------------------------
public static DateTime GetAttribute(this XElement root, string name, DateTime defaultValue)
{
    string strValue = (string)root.Attributes(name).FirstOrDefault() ?? defaultValue.ToString();
    strValue = strValue.ToUpper().Replace("AS OF:", "").Trim();
    DateTime value;
    if (!DateTime.TryParse(strValue, out value))
    {
        throw new Exception(string.Format("Attribute {0}: Value retrieved was not a valid DateTime", name));
    }
    return value;
}

EDIT (07/07/2014) ======================

I've never really been happy with this code, so I refactored it to just two methods using generics:

//--------------------------------------------------------------------------------
public static T GetValue<T>(this XElement root, string name, T defaultValue)
{
    T value = defaultValue;
    string strValue = (string)root.Elements(name).FirstOrDefault() ?? defaultValue.ToString();

    if (value is string)
    {
        return ((T)(object)strValue);
    }
    else
    {
        var tryParse = typeof (T).GetMethod("TryParse", new [] {typeof(string), typeof(T).MakeByRefType()});
        if (tryParse == null)
        {
            throw new InvalidOperationException();
        }
        var parameters = new object[] {strValue, value};
        if ((bool)tryParse.Invoke(null, parameters))
        {
            value = (T)parameters[1];
        }  
    }
    return value;
}

//--------------------------------------------------------------------------------
public static T GetAttribute<T>(this XElement root, string name, T defaultValue)
{
    T value = defaultValue;
    string strValue = (string)root.Attributes(name).FirstOrDefault() ?? defaultValue.ToString();
    if (value is string)
    {
        value = (T)(object)strValue;
    }
    else
    {
        var tryParse = typeof (T).GetMethod("TryParse", new [] {typeof(string), typeof(T).MakeByRefType()});
        if (tryParse == null)
        {
            throw new InvalidOperationException();
        }
        var parameters = new object[] {strValue, value};
        if ((bool)tryParse.Invoke(null, parameters))
        {
            value = (T)parameters[1];
        }
    }
    return value;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Paddedwall Software
United States United States
I've been paid as a programmer since 1982 with experience in Pascal, and C++ (both self-taught), and began writing Windows programs in 1991 using Visual C++ and MFC. In the 2nd half of 2007, I started writing C# Windows Forms and ASP.Net applications, and have since done WPF, Silverlight, WCF, web services, and Windows services.

My weakest point is that my moments of clarity are too brief to hold a meaningful conversation that requires more than 30 seconds to complete. Thankfully, grunts of agreement are all that is required to conduct most discussions without committing to any particular belief system.

Comments and Discussions

 
GeneralMy vote of 2 Pin
jtmueller7-Jan-15 13:58
jtmueller7-Jan-15 13:58 
QuestionExplicit cast operators Pin
Richard Deeming18-Jul-14 5:44
mveRichard Deeming18-Jul-14 5:44 
GeneralRe: Explicit cast operators Pin
PIEBALDconsult18-Jul-14 6:03
mvePIEBALDconsult18-Jul-14 6:03 
GeneralRe: Explicit cast operators Pin
Richard Deeming18-Jul-14 6:15
mveRichard Deeming18-Jul-14 6:15 
GeneralRe: Explicit cast operators Pin
PIEBALDconsult18-Jul-14 6:55
mvePIEBALDconsult18-Jul-14 6:55 
GeneralMy vote of 5 Pin
CatchExAs10-Jul-14 13:12
professionalCatchExAs10-Jul-14 13:12 
GeneralRe: My vote of 5 Pin
#realJSOP11-Jul-14 3:16
mve#realJSOP11-Jul-14 3:16 
QuestionEvolving Code Pin
#realJSOP7-Jul-14 5:28
mve#realJSOP7-Jul-14 5:28 
AnswerRe: Evolving Code Pin
James Curran7-Jul-14 6:08
James Curran7-Jul-14 6:08 
GeneralRe: Evolving Code Pin
#realJSOP7-Jul-14 6:55
mve#realJSOP7-Jul-14 6:55 
GeneralRe: Evolving Code Pin
James Curran7-Jul-14 8:24
James Curran7-Jul-14 8:24 
GeneralRe: Evolving Code Pin
#realJSOP7-Jul-14 14:30
mve#realJSOP7-Jul-14 14:30 
GeneralRe: Evolving Code Pin
James Curran7-Jul-14 11:04
James Curran7-Jul-14 11:04 
GeneralRe: Evolving Code Pin
#realJSOP7-Jul-14 14:31
mve#realJSOP7-Jul-14 14:31 
GeneralRe: Evolving Code Pin
James Curran7-Jul-14 18:07
James Curran7-Jul-14 18:07 
GeneralRe: Evolving Code Pin
Brisingr Aerowing10-Jul-14 13:28
professionalBrisingr Aerowing10-Jul-14 13:28 
GeneralRe: Evolving Code Pin
Brisingr Aerowing10-Jul-14 13:38
professionalBrisingr Aerowing10-Jul-14 13:38 
GeneralRe: Evolving Code Pin
#realJSOP11-Jul-14 3:13
mve#realJSOP11-Jul-14 3:13 
General"throw new Exception(...)" Not a good idea - calling code w... Pin
Richard Deeming25-Jan-11 10:37
mveRichard Deeming25-Jan-11 10:37 
GeneralI use a similar pattern for application settings. Pin
HalLesesne1-Nov-10 16:46
HalLesesne1-Nov-10 16:46 
GeneralGood use of extension methods Pin
Gregory Gadow26-Apr-10 17:01
Gregory Gadow26-Apr-10 17:01 
GeneralRe: My vote of 1 Pin
#realJSOP26-Apr-10 11:48
mve#realJSOP26-Apr-10 11:48 
GeneralRe: My vote of 1 Pin
Rajesh R Subramanian26-Apr-10 11:53
professionalRajesh R Subramanian26-Apr-10 11:53 
GeneralRe: My vote of 1 Pin
Mark_Wallace26-Apr-10 20:37
Mark_Wallace26-Apr-10 20:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.