Jimmy
Jimmy

Reputation: 3274

Getting all messages from InnerException(s)?

Is there any way to write a LINQ style "short hand" code for walking to all levels of InnerException(s) of Exception thrown? I would prefer to write it in place instead of calling an extension function (as below) or inheriting the Exception class.

static class Extensions
{
    public static string GetaAllMessages(this Exception exp)
    {
        string message = string.Empty;
        Exception innerException = exp;

        do
        {
            message = message + (string.IsNullOrEmpty(innerException.Message) ? string.Empty : innerException.Message);
            innerException = innerException.InnerException;
        }
        while (innerException != null);

        return message;
    }
}; 

Upvotes: 110

Views: 76523

Answers (16)

Vlad Gonchar
Vlad Gonchar

Reputation: 505

How about this code:

private static string GetExceptionMessages(this Exception e, string msgs = "")
{
  if (e == null) return string.Empty;
  if (msgs == "") msgs = e.Message;
  if (e.InnerException != null)
    msgs += "\r\nInnerException: " + GetExceptionMessages(e.InnerException);
  return msgs;
}

Usage:

Console.WriteLine(e.GetExceptionMessages())

Example of output:

There was no endpoint listening at http://nnn.mmm.kkk.ppp:8000/routingservice/router that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details.

InnerException: Unable to connect to the remote server

InnerException: No connection could be made because the target machine actively refused it 127.0.0.1:8000

Upvotes: 36

Neomaster
Neomaster

Reputation: 238

With AggregateException

public static List<Exception> GetInnerExceptions(this Exception e)
{
    List<Exception> eList = new List<Exception>();
            
    if (e is AggregateException)
    {
        eList.AddRange((e as AggregateException).InnerExceptions);
    }
    else
    {
        eList.Add(e);
    }

    List<Exception> ieList = eList
        .Where(i => i.InnerException != null)
        .SelectMany(i => i.InnerException.GetInnerExceptions())
        .ToList();

    eList.AddRange(ieList);

    return eList;
}

enter image description here

Upvotes: 2

Mr.B
Mr.B

Reputation: 3797

I used combination of Select and Join:

Unit test:

  [Test]
    public void FirendlyErrorMessage_Tests()
    {
        // Arrange
        Exception ex = new AggregateException(new Exception("MY_ERROR_MESSAGE_FROM_DCE_1"), new Exception("MY_ERROR_MESSAGE_FROM_DCE_2"), new Exception("MY_ERROR_MESSAGE_FROM_DCE_3"));

      
        // Assert
        var e = Assert.Throws<Exception>(() => ErrorHandler.RaiseFirendlyErrorMessage(ex));

        Assert.AreEqual(e.Message, "One or more errors occurred. Possible reasons: MY_ERROR_MESSAGE_1, MY_ERROR_MESSAGE_2, MY_ERROR_MESSAGE_3");
    }

ErrorHandler class:

    public void RaiseFirendlyErrorMessage(Exception ex)
    {
        if (ex is AggregateException)
        {
            var aggrEx = ex as AggregateException;
            string aggregateExcMessage = ex.Message + $" Possible reasons: { string.Join(", ", aggrEx.InnerExceptions.Select(s => s.Message)) }";
            throw new Exception(aggregateExcMessage);
        }
    }

Final message will be:

"One or more errors occurred. Possible reasons: MY_ERROR_MESSAGE_1, MY_ERROR_MESSAGE_2, MY_ERROR_MESSAGE_3"

Upvotes: 0

salvis
salvis

Reputation: 11

After 9+ years the original question still begs for an answer.

Not exactly short, but yes, it can be done LINQ-style in a single statement:

var ex1 = new NullReferenceException("EX1");
var ex2 = new InvalidCastException("EX2", ex1);
var ex3 = new InvalidOperationException("EX3", ex2);
const int maxDepth = 10;

var message = Enumerable.Range(1, maxDepth).Aggregate(
  new { s = $"{ex3.GetType().Name} - {ex3.Message}", ex = ex3.InnerException },
  (v, i) => v.ex != null
            ? new { s = v.s + $"\nInner exception {i}: {v.ex.GetType().Name} - {v.ex.Message}",
                    ex = v.ex.InnerException }
            : new { s = v.s, ex = (Exception)null },
  v => v.s);

/* message is:
InvalidOperationException - EX3
Inner exception 1: InvalidCastException - EX2
Inner exception 2: NullReferenceException - EX1
*/

The key is to use Enumerable.Range().Aggregate() for iteration and a value v of anonymous type (introduced in C# 3.0) holding both

  • the result v.s being built up, as well as
  • the current exception v.ex as we're walking down the list.

(StringBuilder left out to reduce clutter.)

Upvotes: 0

Joshua Folivi
Joshua Folivi

Reputation: 19

Just use the following code.

catch(Exception ex)
{
    Exception currentEx = ex;
    while (currentEx.InnerException != null)
    {
        currentEx = currentEx.InnerException;
    }

    return currentEx;
}

Upvotes: 1

Jiř&#237; Hern&#237;k
Jiř&#237; Hern&#237;k

Reputation: 2477

For those, who are waiting for a one-liner.

exc.ToString();

This will go through all your inner exceptions and return all messages, the downside is that it will also contain stack traces, etc.

Upvotes: 37

MovGP0
MovGP0

Reputation: 7792

Most solutions presended here have the following implementation errors:

  • handle null exceptions
  • handle the inner exceptions of AggregateException
  • define a max depth for recurse inner exceptions (ie. with circular dependencies)

A better implementation is this here:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public static string AggregateMessages(this Exception ex) =>
    ex.GetInnerExceptions()
        .Aggregate(
            new StringBuilder(),
            (sb, e) => sb.AppendLine(e.Message),
            sb => sb.ToString());

public static IEnumerable<Exception> GetInnerExceptions(this Exception ex, int maxDepth = 5)
{
    if (ex == null || maxDepth <= 0)
    {
        yield break;
    }

    yield return ex;

    if (ex is AggregateException ax)
    {
        foreach(var i in ax.InnerExceptions.SelectMany(ie => GetInnerExceptions(ie, maxDepth - 1)))
            yield return i;
    }

    foreach (var i in GetInnerExceptions(ex.InnerException, maxDepth - 1))
        yield return i;
}

Example usage:

try
{
    // ...
}
catch(Exception e)
{
    Log.Error(e, e.AggregateMessages());
}

Upvotes: 6

4thex
4thex

Reputation: 1166

You don't need extension methods or recursive calls:

try {
  // Code that throws exception
}
catch (Exception e)
{
  var messages = new List<string>();
  do
  {
    messages.Add(e.Message);
    e = e.InnerException;
  }
  while (e != null) ;
  var message = string.Join(" - ", messages);
}

Upvotes: 31

Dmitry Karpenko
Dmitry Karpenko

Reputation: 592

I'm just going to leave the most concise version here:

public static class ExceptionExtensions
{
    public static string GetMessageWithInner(this Exception ex) =>
        string.Join($";{ Environment.NewLine }caused by: ",
            GetInnerExceptions(ex).Select(e => $"'{ e.Message }'"));

    public static IEnumerable<Exception> GetInnerExceptions(this Exception ex)
    {
        while (ex != null)
        {
            yield return ex;
            ex = ex.InnerException;
        }
    }
}

Upvotes: 3

    public static string GetExceptionMessage(Exception ex)
    {
        if (ex.InnerException == null)
        {
            return string.Concat(ex.Message, System.Environment.NewLine, ex.StackTrace);
        }
        else
        {
            // Retira a última mensagem da pilha que já foi retornada na recursividade anterior
            // (senão a última exceção - que não tem InnerException - vai cair no último else, retornando a mesma mensagem já retornada na passagem anterior)
            if (ex.InnerException.InnerException == null)
                return ex.InnerException.Message;
            else
                return string.Concat(string.Concat(ex.InnerException.Message, System.Environment.NewLine, ex.StackTrace), System.Environment.NewLine, GetExceptionMessage(ex.InnerException));
        }
    }

Upvotes: 5

k.m
k.m

Reputation: 31484

You mean something like this?

public static class Extensions
{
    public static IEnumerable<Exception> GetInnerExceptions(this Exception ex)
    {
        if (ex == null)
        {
            throw new ArgumentNullException("ex");
        }

        var innerException = ex;
        do
        {
            yield return innerException;
            innerException = innerException.InnerException;
        }
        while (innerException != null);
    }
}

This way you could LINQ over your entire exceptions hierarchy, like this:

exception.GetInnerExceptions().Where(e => e.Message == "Oops!");

Upvotes: 102

Shaggy
Shaggy

Reputation: 59

To add to others, you may want to let the user decide on how to separate the messages:

    public static string GetAllMessages(this Exception ex, string separator = "\r\nInnerException: ")
    {
        if (ex.InnerException == null)
            return ex.Message;

        return ex.Message + separator + GetAllMessages(ex.InnerException, separator);
    }

Upvotes: 5

Jeff Mercado
Jeff Mercado

Reputation: 134611

Unfortunately LINQ doesn't offer methods that could process hierarchical structures, only collections.

I actually have some extension methods that could help do this. I don't have the exact code in hand but they're something like this:

// all error checking left out for brevity

// a.k.a., linked list style enumerator
public static IEnumerable<TSource> FromHierarchy<TSource>(
    this TSource source,
    Func<TSource, TSource> nextItem,
    Func<TSource, bool> canContinue)
{
    for (var current = source; canContinue(current); current = nextItem(current))
    {
        yield return current;
    }
}

public static IEnumerable<TSource> FromHierarchy<TSource>(
    this TSource source,
    Func<TSource, TSource> nextItem)
    where TSource : class
{
    return FromHierarchy(source, nextItem, s => s != null);
}

Then in this case you could do this to enumerate through the exceptions:

public static string GetaAllMessages(this Exception exception)
{
    var messages = exception.FromHierarchy(ex => ex.InnerException)
        .Select(ex => ex.Message);
    return String.Join(Environment.NewLine, messages);
}

Upvotes: 107

Kishore Kumar
Kishore Kumar

Reputation: 12884

public static class ExceptionExtensions
{
    public static IEnumerable<Exception> GetAllExceptions(this Exception ex)
    {
        Exception currentEx = ex;
        yield return currentEx;
        while (currentEx.InnerException != null)
        {
            currentEx = currentEx.InnerException;
            yield return currentEx;
        }
    }

    public static IEnumerable<string> GetAllExceptionAsString(this Exception ex)
    {            
        Exception currentEx = ex;
        yield return currentEx.ToString();
        while (currentEx.InnerException != null)
        {
            currentEx = currentEx.InnerException;
            yield return currentEx.ToString();
        }            
    }

    public static IEnumerable<string> GetAllExceptionMessages(this Exception ex)
    {
        Exception currentEx = ex;
        yield return currentEx.Message;
        while (currentEx.InnerException != null)
        {
            currentEx = currentEx.InnerException;
            yield return currentEx.Message;
        }
    }
}

Upvotes: 2

Trevor Pilley
Trevor Pilley

Reputation: 16423

I don't think so, exception is not an IEnumerable so you can't perform a linq query against one on its own.

An extension method to return the inner exceptions would work like this

public static class ExceptionExtensions
{
    public static IEnumerable<Exception> InnerExceptions(this Exception exception)
    {
        Exception ex = exception;

        while (ex != null)
        {
            yield return ex;
            ex = ex.InnerException;
        }
    }
}

you could then append all the messages using a linq query like this:

var allMessageText = string.Concat(exception.InnerExceptions().Select(e => e.Message + ","));

Upvotes: 8

Christian.K
Christian.K

Reputation: 49310

LINQ is generally used to work with collections of objects. However, arguably, in your case there is no collection of objects (but a graph). So even though some LINQ code might be possible, IMHO it would be rather convoluted or artificial.

On the other hand, your example looks like a prime example where extension methods are actually reasonable. Not to speak of issues like reuse, encapsulation, etc.

I would stay with an extension method, although I might have implemented it that way:

public static string GetAllMessages(this Exception ex)
{
   if (ex == null)
     throw new ArgumentNullException("ex");

   StringBuilder sb = new StringBuilder();

   while (ex != null)
   {
      if (!string.IsNullOrEmpty(ex.Message))
      {
         if (sb.Length > 0)
           sb.Append(" ");

         sb.Append(ex.Message);
      }

      ex = ex.InnerException;
   }

   return sb.ToString();
}

But that is largely an issue of taste.

Upvotes: 11

Related Questions