# 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
istIErrorHandler 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