Ausnahmebehandlung

Ausnahmebehandlung

# Benutzerdefinierte Ausnahmen erstellen

Sie dürfen benutzerdefinierte Ausnahmen implementieren, die wie jede andere Ausnahme ausgelöst werden können. Dies ist sinnvoll, wenn Sie Ihre Ausnahmen während der Laufzeit von anderen Fehlern unterscheidbar machen möchten.

In diesem Beispiel erstellen wir eine benutzerdefinierte Ausnahme zur klaren Behandlung von Problemen, die die Anwendung möglicherweise beim Analysieren einer komplexen Eingabe hat.

# Erstellen einer benutzerdefinierten Ausnahmeklasse

Um eine benutzerdefinierte Ausnahme zu erstellen, erstellen Sie eine Unterklasse von Exception :

public class ParserException : Exception
{
    public ParserException() : 
      base("The parsing went wrong and we have no additional information.") { }
}

Benutzerdefinierte Ausnahmen sind sehr nützlich, wenn Sie dem Catcher zusätzliche Informationen bereitstellen möchten:

public class ParserException : Exception
{
    public ParserException(string fileName, int lineNumber) : 
      base($"Parser error in {fileName}:{lineNumber}") 
    {
      FileName = fileName;
      LineNumber = lineNumber;
    }
    public string FileName {get; private set;}
    public int LineNumber {get; private set;}    
}

Wenn Sie jetzt catch(ParserException x) Sie haben zusätzliche Semantik zur Feinabstimmung der Ausnahmebehandlung.

Benutzerdefinierte Klassen können die folgenden Funktionen implementieren, um zusätzliche Szenarien zu unterstützen.

# erneutes Werfen

Während des Parsing-Prozesses ist die ursprüngliche Ausnahme weiterhin von Interesse. In diesem Beispiel ist es ein FormatException da der Code versucht, ein Stück Zeichenfolge zu analysieren, von dem erwartet wird, dass es sich um eine Zahl handelt. In diesem Fall sollte die benutzerdefinierte Ausnahme die Einbeziehung der 'InnerException unterstützen ':

//new constructor:
ParserException(string msg, Exception inner) : base(msg, inner) {
}

# Serialisierung

In einigen Fällen müssen Ihre Ausnahmen möglicherweise AppDomain-Grenzen überschreiten. Dies ist der Fall, wenn Ihr Parser in seiner eigenen Anwendungsdomäne ausgeführt wird, um das erneute Laden neuer Parserkonfigurationen im laufenden Betrieb zu unterstützen. In Visual Studio können Sie Exception verwenden Vorlage, um Code wie diesen zu generieren.

[Serializable]
public class ParserException : Exception
{
    // Constructor without arguments allows throwing your exception without
    // providing any information, including error message. Should be included
    // if your exception is meaningful without any additional details. Should
    // set message by calling base constructor (default message is not helpful).
    public ParserException()
        : base("Parser failure.")
    {}

    // Constructor with message argument allows overriding default error message.
    // Should be included if users can provide more helpful messages than
    // generic automatically generated messages.
    public ParserException(string message) 
        : base(message)
    {}

    // Constructor for serialization support. If your exception contains custom
    // properties, read their values here.
    protected ParserException(SerializationInfo info, StreamingContext context) 
        : base(info, context)
    {}
}

# Verwendung der ParserException

try
{
    Process.StartRun(fileName)
}
catch (ParserException ex)
{
    Console.WriteLine($"{ex.Message} in ${ex.FileName}:${ex.LineNumber}");
}
catch (PostProcessException x) 
{
    ...
}

Sie können auch benutzerdefinierte Ausnahmen zum Abfangen und Umschließen von Ausnahmen verwenden. Auf diese Weise können viele verschiedene Fehler in einen einzigen Fehlertyp konvertiert werden, der für die Anwendung nützlicher ist:

try
{
    int foo = int.Parse(token);
}
catch (FormatException ex)
{
    //Assuming you added this constructor
    throw new ParserException(
      $"Failed to read {token} as number.", 
      FileName, 
      LineNumber, 
      ex);
}

Wenn Sie Ausnahmen behandeln, indem Sie Ihre eigenen benutzerdefinierten Ausnahmen auslösen, sollten Sie im Allgemeinen einen Verweis auf die ursprüngliche Ausnahme in InnerException einfügen Eigenschaft, wie oben gezeigt.

# Sicherheitsbedenken

Wenn das Offenlegen des Grunds für die Ausnahme die Sicherheit beeinträchtigen könnte, indem Benutzern ermöglicht wird, das Innenleben Ihrer Anwendung zu sehen, kann es eine schlechte Idee sein, die innere Ausnahme zu umschließen. Dies kann zutreffen, wenn Sie eine Klassenbibliothek erstellen, die von anderen verwendet wird.

So können Sie eine benutzerdefinierte Ausnahme auslösen, ohne die innere Ausnahme umzubrechen:

try
{
  // ...
}
catch (SomeStandardException ex)
{
  // ...
  throw new MyCustomException(someMessage);
}

# Fazit

Beim Auslösen einer benutzerdefinierten Ausnahme (entweder mit Wrapping oder mit einer unwrapped neuen Ausnahme) sollten Sie eine Ausnahme auslösen, die für den Aufrufer von Bedeutung ist. Beispielsweise weiß ein Benutzer einer Klassenbibliothek möglicherweise nicht viel darüber, wie diese Bibliothek ihre interne Arbeit erledigt. Die Ausnahmen, die von den Abhängigkeiten der Klassenbibliothek ausgelöst werden, sind nicht sinnvoll. Vielmehr möchte der Benutzer eine Ausnahme, die für die fehlerhafte Verwendung dieser Abhängigkeiten durch die Klassenbibliothek relevant ist.

try
{
  // ...
}
catch (IOException ex)
{
  // ...
  throw new StorageServiceException(@"The Storage Service encountered a problem saving
your data. Please consult the inner exception for technical details. 
If you are not able to resolve the problem, please call 555-555-1234 for technical       
assistance.", ex);
}

# Schlussendlich blockieren

try
{
    /* code that could throw an exception */
}
catch (Exception)
{
    /* handle the exception */
}
finally
{
    /* Code that will be executed, regardless if an exception was thrown / caught or not */
}

Die try / catch / finally block kann beim Lesen aus Dateien sehr praktisch sein.
Zum Beispiel:

FileStream f = null;

try
{
    f = File.OpenRead("file.txt");
    /* process the file here */
}
finally
{
    f?.Close(); // f may be null, so use the null conditional operator.
}

Auf einen try-Block muss entweder ein catch folgen oder ein finally Block. Da es jedoch keinen catch-Block gibt, führt die Ausführung zum Abbruch. Vor der Beendigung werden die Anweisungen innerhalb des finally-Blocks ausgeführt.

Beim File-Reading hätten wir auch einen using verwenden können Block als FileStream (was OpenRead return) implementiert IDisposable .

Auch wenn dort ein return steht Anweisung in try Block, der finally Block wird normalerweise ausgeführt; Es gibt einige Fälle, in denen dies nicht der Fall ist:

  • Wenn ein StackOverflow auftritt .
  • Environment.FailFast
  • Der Anwendungsprozess wird beendet, normalerweise von einer externen Quelle.

# Best Practices

# Cheatsheet

TUN NICHT
Flusssteuerung mit Kontrollanweisungen Flusssteuerung mit Ausnahmen
Verfolgen Sie ignorierte (absorbierte) Ausnahmen durch Protokollierung Ausnahme ignorieren
Ausnahme wiederholen mit throw Ausnahme erneut auslösen - throw new ArgumentNullException() oder throw ex
Vordefinierte Systemausnahmen auslösen Werfen Sie benutzerdefinierte Ausnahmen ähnlich vordefinierten Systemausnahmen aus
Werfen Sie eine benutzerdefinierte/vordefinierte Ausnahme aus, wenn dies für die Anwendungslogik entscheidend ist Werfen Sie benutzerdefinierte/vordefinierte Ausnahmen aus, um eine Warnung im Ablauf auszugeben
Erfassen Sie Ausnahmen, die Sie behandeln möchten Jede Ausnahme abfangen

# Geschäftslogik NICHT mit Ausnahmen verwalten.

Die Flusskontrolle sollte NICHT durch Ausnahmen erfolgen. Verwenden Sie stattdessen bedingte Anweisungen. Eine Kontrolle kann mit if-else erfolgen Anweisung klar, verwenden Sie keine Ausnahmen, da dies die Lesbarkeit und Leistung verringert.

Betrachten Sie das folgende Snippet von Mr. Bad Practices:

// This is a snippet example for DO NOT
object myObject;
void DoingSomethingWithMyObject()
{
    Console.WriteLine(myObject.ToString());
}

Wenn die Ausführung Console.WriteLine(myObject.ToString()); erreicht Die Anwendung löst eine NullReferenceException aus. Mr. Bad Practices erkannte, dass myObject ist null und hat sein Snippet bearbeitet, um NullReferenceException zu erfassen und zu handhaben :

// This is a snippet example for DO NOT
object myObject;
void DoingSomethingWithMyObject()
{
    try
    {
        Console.WriteLine(myObject.ToString());
    }
    catch(NullReferenceException ex)
    {
        // Hmmm, if I create a new instance of object and assign it to myObject:
        myObject = new object();
        // Nice, now I can continue to work with myObject
        DoSomethingElseWithMyObject();
    }
}

Da das vorherige Snippet nur die Logik der Ausnahme abdeckt, was soll ich tun, wenn myObject ist an dieser Stelle nicht null? Wo soll ich diesen Teil der Logik behandeln? Direkt nach Console.WriteLine(myObject.ToString()); ? Wie wäre es nach dem try...catch blockieren?

Wie wäre es mit Mr. Best Practices? Wie würde er damit umgehen?

// This is a snippet example for DO
object myObject;
void DoingSomethingWithMyObject()
{
    if(myObject == null)
        myObject = new object();
    
    // When execution reaches this point, we are sure that myObject is not null
    DoSomethingElseWithMyObject();
}

Mr. Best Practices erreichte die gleiche Logik mit weniger Code und einer klaren und verständlichen Logik.

# Ausnahmen NICHT erneut auslösen

Das erneute Auslösen von Ausnahmen ist teuer. Es wirkt sich negativ auf die Leistung aus. Für Code, der routinemäßig fehlschlägt, können Sie Entwurfsmuster verwenden, um Leistungsprobleme zu minimieren. In diesem Thema werden zwei Entwurfsmuster beschrieben, die nützlich sind, wenn Ausnahmen die Leistung erheblich beeinträchtigen können.

# Ausnahmen NICHT ohne Protokollierung absorbieren

try
{
    //Some code that might throw an exception
}
catch(Exception ex)
{
    //empty catch block, bad practice
}

Schlucken Sie niemals Ausnahmen. Das Ignorieren von Ausnahmen wird diesen Moment retten, aber später ein Chaos für die Wartbarkeit verursachen. Beim Protokollieren von Ausnahmen sollten Sie immer die Ausnahmeinstanz protokollieren, damit der vollständige Stack-Trace protokolliert wird und nicht nur die Ausnahmemeldung.

try
{
    //Some code that might throw an exception
}
catch(NullException ex)
{
    LogManager.Log(ex.ToString());
}

# Fangen Sie keine Ausnahmen ab, die Sie nicht behandeln können

Viele Ressourcen wie diese fordern Sie dringend auf, darüber nachzudenken, warum Sie eine Ausnahme an der Stelle abfangen, an der Sie sie abfangen. Sie sollten eine Ausnahme nur dann abfangen, wenn Sie sie an dieser Stelle behandeln können. Wenn Sie dort etwas tun können, um das Problem zu mindern, z. B. einen alternativen Algorithmus ausprobieren, eine Verbindung zu einer Sicherungsdatenbank herstellen, einen anderen Dateinamen ausprobieren, 30 Sekunden warten und es erneut versuchen oder einen Administrator benachrichtigen, können Sie den Fehler abfangen und das tun. Wenn es nichts gibt, was Sie plausibel und vernünftig tun können, "lassen Sie es einfach los" und lassen Sie die Ausnahme auf einer höheren Ebene behandeln. Wenn die Ausnahme katastrophal genug ist und es keine andere vernünftige Möglichkeit gibt, als das gesamte Programm wegen der Schwere des Problems abzustürzen, lassen Sie es abstürzen.

try
{
    //Try to save the data to the main database.
}
catch(SqlException ex)
{
    //Try to save the data to the alternative database.
}
//If anything other than a SqlException is thrown, there is nothing we can do here. Let the exception bubble up to a level where it can be handled.

# Ausnahme-Anti-Patterns

# Schluckausnahmen

Eine Ausnahme sollte immer wie folgt erneut ausgelöst werden:

try
{
    ...
}
catch (Exception ex)
{
    ...
    throw;
}

Wenn Sie eine Ausnahme wie unten erneut auslösen, wird die ursprüngliche Ausnahme verschleiert und der ursprüngliche Stack-Trace geht verloren. Das sollte man niemals tun! Der Stack-Trace vor dem Catch and Rethrow geht verloren.

try
{
    ...
}
catch (Exception ex)
{
    ...
    throw ex;
}

# Baseball-Ausnahmebehandlung

Man sollte Ausnahmen nicht als Ersatz für normale Ablaufsteuerungskonstrukte wie If-Then-Anweisungen und While-Schleifen verwenden. Dieses Antimuster wird manchmal als Baseball-Ausnahmebehandlung bezeichnet.

Hier ist ein Beispiel für das Anti-Pattern:

try
{
    while (AccountManager.HasMoreAccounts())
    {
        account = AccountManager.GetNextAccount();
        if (account.Name == userName)
        {
            //We found it
            throw new AccountFoundException(account);
        }
    }
}
catch (AccountFoundException found)
{
    Console.Write("Here are your account details: " + found.Account.Details.ToString());
}

Hier ist ein besserer Weg, es zu tun:

Account found = null;
while (AccountManager.HasMoreAccounts() && (found==null))
{
    account = AccountManager.GetNextAccount();
    if (account.Name == userName)
    {
        //We found it
        found = account;
    }
}
Console.Write("Here are your account details: " + found.Details.ToString());

# Fang (Ausnahme)

Es gibt fast keine (manche sagen gar keine!) Gründe, den generischen Ausnahmetyp in Ihrem Code abzufangen. Sie sollten nur die Ausnahmetypen abfangen, die Sie erwarten, da Sie sonst Fehler in Ihrem Code verbergen.

try 
{
     var f = File.Open(myfile);
     // do something
}
catch (Exception x)
{
     // Assume file not found
     Console.Write("Could not open file");
     // but maybe the error was a NullReferenceException because of a bug in the file handling code?
}

Besser:

try 
{
     var f = File.Open(myfile);
     // do something which should normally not throw exceptions
}
catch (IOException)
{
     Console.Write("File not found");
}
// Unfortunatelly, this one does not derive from the above, so declare separatelly
catch (UnauthorizedAccessException) 
{
     Console.Write("Insufficient rights");
}

Wenn eine andere Ausnahme auftritt, lassen wir die Anwendung absichtlich abstürzen, damit sie direkt in den Debugger wechselt und wir das Problem beheben können. Wir dürfen ohnehin kein Programm ausliefern, bei dem andere Ausnahmen als diese auftreten, also ist ein Absturz kein Problem.

Das Folgende ist ebenfalls ein schlechtes Beispiel, da es Ausnahmen verwendet, um einen Programmierfehler zu umgehen. Dafür sind sie nicht ausgelegt.

public void DoSomething(String s)
{
     if (s == null)
         throw new ArgumentNullException(nameof(s));
     // Implementation goes here
}

try 
{    
     DoSomething(myString);
}
catch(ArgumentNullException x)
{
    // if this happens, we have a programming error and we should check
    // why myString was null in the first place.
}

# Grundlegende Ausnahmebehandlung

try
{
    /* code that could throw an exception */
}
catch (Exception ex)
{
    /* handle the exception */
}

Beachten Sie, dass die Behandlung aller Ausnahmen mit demselben Code oft nicht der beste Ansatz ist.
Dies wird häufig als letzter Ausweg verwendet, wenn interne Ausnahmebehandlungsroutinen fehlschlagen.

# Umgang mit bestimmten Ausnahmetypen

try
{
    /* code to open a file */
}
catch (System.IO.FileNotFoundException)
{
    /* code to handle the file being not found */
}
catch (System.IO.UnauthorizedAccessException)
{
    /* code to handle not being allowed access to the file */
}
catch (System.IO.IOException)
{
    /* code to handle IOException or it's descendant other than the previous two */
}
catch (System.Exception)
{
    /* code to handle other errors */
}

Achten Sie darauf, dass Ausnahmen der Reihe nach ausgewertet werden und die Vererbung angewendet wird. Sie müssen also mit den spezifischsten beginnen und mit ihrem Vorfahren enden. An jedem beliebigen Punkt wird nur ein Catch-Block ausgeführt.

# Aggregierte Ausnahmen / mehrere Ausnahmen von einer Methode

Wer sagt, dass Sie nicht mehrere Ausnahmen in einer Methode auslösen können? Wenn Sie es nicht gewohnt sind, mit AggregateExceptions herumzuspielen, könnten Sie versucht sein, Ihre eigene Datenstruktur zu erstellen, um darzustellen, dass viele Dinge schief gehen. Natürlich gibt es andere Datenstrukturen, die keine Ausnahme darstellen und idealer wären, wie beispielsweise die Ergebnisse einer Validierung. Selbst wenn Sie mit AggregateExceptions spielen, sind Sie möglicherweise auf der Empfängerseite und handhaben sie immer, ohne zu wissen, dass sie für Sie von Nutzen sein können.

Es ist durchaus plausibel, dass eine Methode ausgeführt wird, und obwohl es sich um einen Fehler als Ganzes handelt, sollten Sie mehrere Dinge hervorheben, die in den ausgelösten Ausnahmen schief gelaufen sind. Als Beispiel kann dieses Verhalten anhand der Funktionsweise paralleler Methoden gesehen werden, wenn eine Aufgabe in mehrere Threads aufgeteilt wurde und eine beliebige Anzahl von ihnen Ausnahmen auslösen könnte, und dies muss gemeldet werden. Hier ist ein dummes Beispiel dafür, wie Sie davon profitieren könnten:


   public void Run()
    {
        try
        {
            this.SillyMethod(1, 2);
        }
        catch (AggregateException ex)
        {
            Console.WriteLine(ex.Message);
            foreach (Exception innerException in ex.InnerExceptions)
            {
                Console.WriteLine(innerException.Message);
            }
        }
    }

    private void SillyMethod(int input1, int input2)
    {
        var exceptions = new List<Exception>();

        if (input1 == 1)
        {
            exceptions.Add(new ArgumentException("I do not like ones"));
        }
        if (input2 == 2)
        {
            exceptions.Add(new ArgumentException("I do not like twos"));
        }
        if (exceptions.Any())
        {
            throw new AggregateException("Funny stuff happended during execution", exceptions);
        }
    }

# Auslösen einer Ausnahme

Ihr Code kann und sollte häufig eine Ausnahme auslösen, wenn etwas Ungewöhnliches passiert ist.

public void WalkInto(Destination destination)
{
    if (destination.Name == "Mordor")
    {
        throw new InvalidOperationException("One does not simply walk into Mordor.");
    }
    // ... Implement your normal walking code here.
}

# Unhandled and Thread Exception

AppDomain.UnhandledException Dieses Ereignis benachrichtigt über nicht abgefangene Ausnahmen. Es ermöglicht der Anwendung, Informationen über die Ausnahme zu protokollieren, bevor der Standardhandler des Systems die Ausnahme an den Benutzer meldet und die Anwendung beendet. Wenn genügend Informationen über den Status der Anwendung verfügbar sind, können andere Aktionen ausgeführt werden durchgeführt – wie das Speichern von Programmdaten für eine spätere Wiederherstellung. Vorsicht ist geboten, da Programmdaten beschädigt werden können, wenn Ausnahmen nicht behandelt werden.


   /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    private static void Main(string[] args)
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledException);            
    }

Application.ThreadException Dieses Ereignis ermöglicht Ihrer Windows Forms-Anwendung, ansonsten nicht behandelte Ausnahmen zu behandeln, die in Windows Forms-Threads auftreten. Hängen Sie Ihre Ereignishandler an das ThreadException-Ereignis an, um diese Ausnahmen zu behandeln, die Ihre Anwendung in einem unbekannten Zustand belassen. Ausnahmen sollten nach Möglichkeit von einem strukturierten Ausnahmebehandlungsblock behandelt werden.


   /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    private static void Main(string[] args)
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledException);
        Application.ThreadException += new ThreadExceptionEventHandler(ThreadException);
    }

Und schließlich Ausnahmebehandlung

static void UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Exception ex = (Exception)e.ExceptionObject;
        // your code
    }

static void ThreadException(object sender, ThreadExceptionEventArgs e)
    {
        Exception ex = e.Exception;
        // your code
    }

# Verwendung des Ausnahmeobjekts

Sie dürfen Ausnahmen in Ihrem eigenen Code erstellen und auslösen. Das Instanziieren einer Ausnahme erfolgt auf die gleiche Weise wie bei jedem anderen C#-Objekt.

Exception ex = new Exception();

// constructor with an overload that takes a message string
Exception ex = new Exception("Error message"); 

Sie können dann den throw verwenden Schlüsselwort zum Auslösen der Ausnahme:

try
{
    throw new Exception("Error");
}
catch (Exception ex)
{
    Console.Write(ex.Message); // Logs 'Error' to the output window
} 

Hinweis: Wenn Sie eine neue Ausnahme innerhalb eines Catch-Blocks auslösen, stellen Sie sicher, dass die ursprüngliche Ausnahme als "innere Ausnahme" übergeben wird, z. B.

void DoSomething() 
{
    int b=1; int c=5;
    try
    {
        var a = 1; 
        b = a - 1;
        c = a / b;
        a = a / c;
    }        
    catch (DivideByZeroException dEx) when (b==0)
    {
        // we're throwing the same kind of exception
        throw new DivideByZeroException("Cannot divide by b because it is zero", dEx);
    }
    catch (DivideByZeroException dEx) when (c==0)
    {
        // we're throwing the same kind of exception
        throw new DivideByZeroException("Cannot divide by c because it is zero", dEx);
    }
}

void Main()
{    
    try
    {
        DoSomething();
    }
    catch (Exception ex)
    {
        // Logs full error information (incl. inner exception)
        Console.Write(ex.ToString()); 
    }    
}

In diesem Fall wird davon ausgegangen, dass die Ausnahme nicht behandelt werden kann, aber der Nachricht werden einige nützliche Informationen hinzugefügt (und die ursprüngliche Ausnahme kann immer noch über ex.InnerException aufgerufen werden durch einen äußeren Ausnahmeblock).

Es zeigt etwas wie:

System.DivideByZeroException:Kann nicht durch b dividieren, da es Null ist ---> System.DivideByZeroException:Es wurde versucht, durch Null zu dividieren.
bei UserQuery.g__DoSomething0_0() in C:[...]\LINQPadQuery.cs:Zeile 36
--- Ende des Stack-Trace der inneren Ausnahme ---
bei UserQuery.g__DoSomething0_0() in C:[...]\LINQPadQuery.cs:Zeile 42
bei UserQuery.Main() in C:[...]\LINQPadQuery.cs:Zeile 55

Wenn Sie dieses Beispiel in LinqPad ausprobieren, werden Sie feststellen, dass die Zeilennummern nicht sehr aussagekräftig sind (sie helfen Ihnen nicht immer). Aber das Übergeben eines hilfreichen Fehlertextes, wie oben vorgeschlagen, reduziert oft erheblich die Zeit, um den Ort des Fehlers aufzuspüren, der in diesem Beispiel eindeutig die Zeile ist

c =a / b;

in Funktion DoSomething() .

Probieren Sie es in .NET Fiddle aus

# IErrorHandler für WCF-Dienste implementieren

Die Implementierung von IErrorHandler für WCF-Dienste ist eine hervorragende Möglichkeit, die Fehlerbehandlung und -protokollierung zu zentralisieren. Die hier gezeigte Implementierung sollte alle nicht behandelten Ausnahmen abfangen, die als Ergebnis eines Aufrufs an einen Ihrer WCF-Dienste ausgelöst werden. In diesem Beispiel wird auch gezeigt, wie ein benutzerdefiniertes Objekt zurückgegeben wird und wie JSON anstelle des Standard-XML zurückgegeben wird.

IErrorHandler implementieren:

using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Runtime.Serialization.Json;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace BehaviorsAndInspectors
{
    public class ErrorHandler : IErrorHandler
    {

        public bool HandleError(Exception ex)
        {
            // Log exceptions here

            return true;

        } // end

        public void ProvideFault(Exception ex, MessageVersion version, ref Message fault)
        {
            // Get the outgoing response portion of the current context
            var response = WebOperationContext.Current.OutgoingResponse;

            // Set the default http status code 
            response.StatusCode = HttpStatusCode.InternalServerError;

            // Add ContentType header that specifies we are using JSON
            response.ContentType = new MediaTypeHeaderValue("application/json").ToString();

            // Create the fault message that is returned (note the ref parameter) with BaseDataResponseContract                
            fault = Message.CreateMessage(
                version,
                string.Empty,
                new CustomReturnType { ErrorMessage = "An unhandled exception occurred!" },
                new DataContractJsonSerializer(typeof(BaseDataResponseContract), new List<Type> { typeof(BaseDataResponseContract) }));

            if (ex.GetType() == typeof(VariousExceptionTypes))
            {
                 // You might want to catch different types of exceptions here and process them differently
            }

            // Tell WCF to use JSON encoding rather than default XML
            var webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Json);
            fault.Properties.Add(WebBodyFormatMessageProperty.Name, webBodyFormatMessageProperty);

        } // end

    } // end class

} // end namespace

In diesem Beispiel hängen wir den Handler an das Dienstverhalten an. Sie können dies auch auf ähnliche Weise an IEndpointBehavior, IContractBehavior oder IOperationBehavior anhängen.

An Dienstverhalten anhängen:

using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace BehaviorsAndInspectors
{
    public class ErrorHandlerExtension : BehaviorExtensionElement, IServiceBehavior
    {
        public override Type BehaviorType
        {
            get { return GetType(); }
        }

        protected override object CreateBehavior()
        {
            return this;
        }

        private IErrorHandler GetInstance()
        {
            return new ErrorHandler();
        }

        void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } // end

        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            var errorHandlerInstance = GetInstance();

            foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
            {
                dispatcher.ErrorHandlers.Add(errorHandlerInstance);
            }
        }

        void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } // end
      
    } // end class

} // end namespace

Konfigurationen in Web.config:

...
<system.serviceModel>

    <services>      
      <service name="WebServices.MyService">
        <endpoint binding="webHttpBinding" contract="WebServices.IMyService" />
      </service>
    </services>

    <extensions>      
      <behaviorExtensions>        
        <!-- This extension if for the WCF Error Handling-->
        <add name="ErrorHandlerBehavior" type="WebServices.BehaviorsAndInspectors.ErrorHandlerExtensionBehavior, WebServices, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />      
      </behaviorExtensions>    
    </extensions>

    <behaviors>          
      <serviceBehaviors>        
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <ErrorHandlerBehavior />
        </behavior>     
      </serviceBehaviors>    
    </behaviors>

    ....
</system.serviceModel>
...

Hier sind einige Links, die zu diesem Thema hilfreich sein können:

https://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.100).aspx

http://www.brainthud.com/cards/5218/25441/welche-vier-verhaltensschnittstellen-für-die-interaktion-mit-einem-dienst-oder-client-beschreiben-welche-methoden-sie-bestehen- implementieren-und

Andere Beispiele:

IErrorHandler gibt einen falschen Nachrichtentext zurück, wenn der HTTP-Statuscode 401 Unauthorized

ist

IErrorHandler scheint meine Fehler in WCF nicht zu behandeln. Irgendwelche Ideen?

Wie lässt sich ein benutzerdefinierter WCF-Fehlerhandler dazu bringen, eine JSON-Antwort mit nicht OK-HTTP-Code zurückzugeben?

Wie legen Sie den Content-Type-Header für eine HttpClient-Anfrage fest?

# Verschachtelung von Ausnahmen und try-catch-Blöcken.

Man kann eine Ausnahme verschachteln / try catch Block innerhalb des anderen.

Auf diese Weise kann man kleine Codeblöcke verwalten, die funktionieren, ohne den gesamten Mechanismus zu stören.

try 
{
//some code here
    try 
    {
        //some thing which throws an exception. For Eg : divide by 0
    }
    catch (DivideByZeroException dzEx)
    {
        //handle here only this exception
        //throw from here will be passed on to the parent catch block
    }
    finally
    {
        //any thing to do after it is done.
    }
 //resume from here & proceed as normal; 
}
catch(Exception e)
{
    //handle here
}

Hinweis: Vermeiden Sie das Schlucken von Ausnahmen, wenn Sie zum übergeordneten Catch-Block werfen