3

What is the correct way to determine exactly what caused an exception, to correct it?

Consider the code below. I attempt to parse a string of XML, but occasionally, the incoming XML will not be top-level, meaning it needs to be surrounded by a root element.

Which this happens, the parser throws an XmlException, but it could throw that for a lot of reasons. I want to catch this one specific reason.

I do this, which I concede is probably not great:

var doc = new XmlDocument();
try
{
    doc.LoadXml(xml);
}
catch(XmlException e)
{
    if(e.Message.Contains("multiple root elements"))
    {
        doc.LoadXml($"<root>{xml}</root>");
    }
    else
    {
        throw e;
    }
}

This feels like a hack. What is the correct way to do this?

Rand Random
  • 7,300
  • 10
  • 40
  • 88
Deane
  • 8,269
  • 12
  • 58
  • 108
  • The xml where is it come from ? – lucky Jan 20 '18 at 17:01
  • Take a look at the example [here](https://msdn.microsoft.com/en-us/library/zcsyk915(v=vs.110).aspx) –  Jan 20 '18 at 17:02
  • You can use XmlException.Data to get more details – KHL Jan 20 '18 at 17:05
  • @KhalilLazhar, weird, every time I get *any* exception, the Data property is empty dictionary... I think I have never seen system exception with filled Data property... :( – David Hruška Jan 20 '18 at 17:07
  • 1
    Your way of rethrowing the exception clears the stacktrace. Not a ideal situation. Nor is string-parsing the Exception message. Here are two good articles on proper exception handling, that I link a lot: http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx http://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET In all likelyhood, this any XML Excpetion is a exogenous Exception. So parsing them in detail is not really your job. – Christopher Jan 20 '18 at 17:08
  • Are you fixing the right problem here? XML is meant to be *structured* data. Why are you not receiving *structured data* and why can it not be fixed in whatever is *producing* this input? – Damien_The_Unbeliever Jan 20 '18 at 17:44
  • If you know in advance you're going to be getting multiple XML fragments concatenated together, you can read them as an `IEnumerable` or `IEnumerable`. There are a bunch of solutions already for doing this, I like the `ReadSubtree()` solution [here](https://stackoverflow.com/q/2374426) and [here](https://stackoverflow.com/q/7199047). You can also use `XmlDocumentFragment` as shown [here](https://stackoverflow.com/q/18186225). All these solutions avoid the hack of surrounding the XML with a fake root element. – dbc Jan 20 '18 at 19:09
  • @Damien_The_Unbeliever I don't control the XML. It comes from outside my code. I would love to fix it before trying to parse, but I don't know of a more reliable way to do that than just trying to parse it and catching the exception. – Deane Jan 20 '18 at 21:04
  • @dbc I do not know in advance. I get a string, and it might have a root element or not, and I have no way of knowing this before I try to parse it. – Deane Jan 20 '18 at 21:04
  • @Deane - the answers to those questions work with both well-formed single-root XML and multi-root concatenated XML fragments. – dbc Jan 21 '18 at 01:03
  • @Deane - the point is - you're trying to solve the wrong problem here. And the solution isn't a *technical* one. You've got people throwing *junk* at you and *claiming* that it's XML when in fact it's text that often *resembles* XML but in fact is not well formed. Talk to your bosses. Explain that you're being given junk that doesn't even validate, let alone conform to a schema. You can try to persevere but if you just let this ride then every subsequent time that your code is handed junk and fails, *you'll* get the blame yet again, because you're already acting as if it's *your* job to cope. – Damien_The_Unbeliever Jan 21 '18 at 16:29

3 Answers3

3

There is a new feature of C# that allows you to filter exceptions in the catch clause with when keyword:

try
{

} 
catch (XmlException ex) when ( ex.Message.Contains(...) )
{
   //handle
}

You can use multiple fields to recognize the exception type, like the InnerException, StackTrace, and Data. As @David Hruška suggests, the HResult property is also a good place to check to recognize the type of the exception

The Message is not the best property to use for the check, as it is usually localized for the built-in types and as a result might look different with other culture setting.

Martin Zikmund
  • 38,440
  • 7
  • 70
  • 91
  • 1
    I want to repeat that String Parsing the message is **not** a good idea. Look if there is any field or contained Exception that can give you more details. – Christopher Jan 20 '18 at 17:09
  • I am not sure, if it solves the problem. You does not identify the *specific exception* as this is manual check for message. But +1 for showing that new feature, I completelly forget about this one :) – David Hruška Jan 20 '18 at 17:09
  • @MartinZikmund, I would add `HResult` as well, we sometimes check for this property. – David Hruška Jan 20 '18 at 17:14
  • Yes I know, it is just an alternative, but it reminded me of this cool feature so as a reminder for both myself and the OP :-). But @David Hruška, your solution is more reliable – Martin Zikmund Jan 20 '18 at 17:16
  • 3
    message may also change in other framework versions which will be a breaking change for your code. never do that. – M.kazem Akhgary Jan 20 '18 at 17:42
1

you can try to make a switch for XmlException.HResult as described here:

https://msdn.microsoft.com/en-us/library/system.xml.xmlexception(v=vs.110).aspx

The only thing I am not sure, if it points the the specific exception type (like XmlException) or specific exception "message".

If this does not help, I think you have no other option than checking for message.

EDIT: Also, as was pointed above, you should throw; instead of throw e; as the second clears the StackTrace. ReSharper also warns about this one.

  • Unfortunately, [`HResult` numbers are not standardized across all platforms](https://stackoverflow.com/a/46381756/) for .NET Standard and .NET Core, so this is only reliable on .NET Framework. I think it is time to pressure Microsoft to come up with something more reliable. – NightOwl888 Jan 20 '18 at 18:45
0

There are multiple ways to achieve what you're trying to do. Different test frameworks bring their own tools to help with this too. For example, if you're using MSVS Test Framework, the simplest option is to only check for the exception type. In this case you just mark the test method with "ExceptedExceptionAttribute" and specify the expected exception type to be thrown from the method, as follows:

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void Action_ThrowsException()
{
    // test logic here
}

There is another constructor available, which allows to specify the exact exception message as the second parameter, which is rarely used. You can find the documentation for ExpectedExceptionAttribute in MSDN.

Another option, where you have more control, would be what was already suggested in the other answers, which can be encapsulated in a helper method as follow:

private static T Throws<T>(Action action) where T : Exception
{
    try
    {
        action();
        Assert.Fail();
    }
    catch (T ex)
    {
        // This exception was expected.
        return ex;
    }
}

Using this helper method, you now can have your test method as follows:

[TestMethod]
public void Action_ThrowsException()
{
    // test logic here
    ArgumentException aex = Throws<ArgumentException>(()=>{
        // Test logic here
    });

    Assert.AreEqual("Expected error message", aex.Message);
}

As you can see, this option provides you with higher flexibility as you can now validate other aspects of the exception explicitly.

FYI: The second solution is given as part of the xUnit framework.

Community
  • 1
  • 1
Artak
  • 2,819
  • 20
  • 31
  • This is actually not meant as part of a UnitTest. This is runtime code. I'm in a situation where sometimes the XML will have a root element, and sometimes it won't, and I have no way of knowing before I try to parse it. – Deane Jan 20 '18 at 21:02
  • Thanks for the additional information. In that case, you can choose one of the following options: 1. Use if statement to branch your logic for both cases 2. Try to parse the most expected way and if that fails, try the other logic. The "if" option (Option #1) is preferable, as it'll be faster and will probably require less resources. This really comes down to identifying whether the XML snippet has a root element or not, is it? If so, I'll be able to share some code to do that. – Artak Jan 20 '18 at 21:20