Verwenden von SOAP-Fehlern

 

Scott Seely
Microsoft Corporation

20. September 2002

Zusammenfassung: Scott Seely zeigt, wie Sie SOAP-Fehler verwenden, um dem Entwickler zur Entwicklungszeit den entsprechenden Detailgrad und dem Kunden zu liefern, während sich der Webdienst in der Produktion befindet. (14 gedruckte Seiten)

Einführung

Wenn in der Entwicklung ein Fehler ausgelöst wird, möchten Sie wissen, wo der Fehler entstanden ist. Da diese Informationen für Consumer des Webdiensts nicht nützlich sind, sollten Sie keine bedeutungslosen Zeilennummern zurückgeben, wenn der Dienst bereitgestellt wird. Stattdessen sollten Sie andere kontextbezogene Informationen zum Geschehen bereitstellen. In dieser Spalte möchte ich einen Blick darauf werfen, wie Sie SOAP-Fehler verwenden, um Ihnen die richtigen Detailebenen zur Entwicklungszeit und ihren Kunden bereitzustellen, während sich der Webdienst in der Produktion befindet. Falls Sie mit dem grundlegenden Layout eines SOAP-Fehlers nicht vertraut sind, werfen wir zunächst einen kurzen Blick auf das SOAP-Fehlerelement.

Das SOAP Fault-Element verfügt über vier separate Elemente:

  • faultcode: Enthält den Wert VersionMismatch, MustUnderstand, Client oder Server. Wir sehen uns an, wie jedes dieser Elemente später in der Spalte festgelegt wird.
  • faultstring: Bietet eine lesbare Erklärung, warum der Fehler aufgetreten ist.
  • faultactor: Gibt den URI an, der dem Akteur zugeordnet ist, der den Fehler im Nachrichtenpfad verursacht hat. Beim Messaging im RPC-Stil sollte der Akteur der URI des aufgerufenen Webdiensts sein.
  • Detail: Enthält Informationen darüber, warum der Fehler aufgetreten ist. Dieses Element kann mehr XML-Elemente enthalten oder nur Nur-Text sein.

Im Allgemeinen entspricht ein Fehler einer Anwendungsausnahmeregelung. Alle SOAP-Toolkits, von denen ich weiß, dass sie einen zurückgegebenen SOAP-Fehler in eine Ausnahme (.NET, verschiedene Java-Stapel) oder einen Fehlercode (Microsoft SOAP Toolkit, SOAP::Lite) konvertieren. Warum Erwähnung ich dies? Wenn Ihr Webdienst einen Fehler zurückgibt, wird der Fehler als Ausnahme für den aufrufenden Code angezeigt. Wenn Sie einen Fehler zurückgeben, ist es wichtig, Ihre Zielgruppe zu kennen. der SOAP-Fehler sollte aussagekräftige Informationen bieten.

Was der Begriff "aussagekräftige Informationen" bedeutet, hängt jedoch von Ihrem Kontext ab. Wenn Sie der Entwickler eines Webdiensts sind und etwas Schlechtes passiert, können aussagekräftige Informationen für instance den genauen Speicherort der Ausnahme innerhalb Ihres Codes und der zugehörigen Aufrufliste bedeuten. Wenn Sie den Webdienst nutzen, werden Sie sehr daran interessiert sein, warum Ihre Eingabe ungültig ist, aber es ist Ihnen möglicherweise egal, welcher serverseitige Code fehlgeschlagen ist.

Um aussagekräftige Fehler zu generieren, sehen wir uns die folgenden verwandten Themen an:

  • Festlegen des entsprechenden Fehlercodes.
  • In Fault/Faultmessage zurückgegebene Daten.
  • Anwendungsausnahmen werden auslaufen.
  • Verwenden des Detailelements.

All dies sehen wir uns im Hinblick auf Microsoft® ASP.NET-Webdienste an. Daher werden wir uns ziemlich stark auf zwei Klassen konzentrieren: System.Web.Services.Protocols.SoapException und System.Web.Services.Protocols.SoapHeaderException. Diese beiden Klassen funktionieren gleich. Der einzige Unterschied: Wenn beim Verarbeiten eines Headers ein Fehler gefunden wird, müssen Sie SoapHeaderException verwenden. Wenn sich der Fehler im Text befindet, verwenden Sie SoapException. Diese Diskussion konzentriert sich auf die Verwendung von SoapException. Beginnen wir diese Diskussion mit dem Festlegen der Fehlermeldung.

Fault/Faultmessage

Wenn der Code in einem Webdienst eine Ausnahme auslöst, fängt ASP.NET diese Ausnahme ab und transformiert sie in einen SOAP-Fehler. Angenommen, unter bestimmten Umständen wird die folgende Zeile ausgeführt, bevor der Webdienst auf den Client reagieren kann:

Throw New Exception("Something bad happened") 

Wenn dieser Code ausgeführt wird, fängt der ASP.NET ASMX-Handler die Ausnahme ab. Wenn Sie das Webdienstprojekt mit Microsoft Visual Studio® .NET generiert und alle Standardeinstellungen allein gelassen haben, sieht der zurückgegebene Fehler wie folgt aus:

<soap:Fault>
   <faultcode>soap:Server</faultcode>
   <faultstring>System.Web.Services.Protocols.SoapException: Server was 
unable to process request. ---&gt; System.Exception: Something bad 
happened at AYS17Sept2002.Service1.CallFault() in 
c:\inetpub\wwwroot\AYS17Sept2002\Service1.asmx.vb:line 49
   --- End of inner exception stack trace ---</faultstring>
   <detail />
</soap:Fault>

Diese Art von Informationen eignet sich hervorragend, wenn Sie den Webdienst entwickeln und Clients schreiben, um den Code auszuführen. Es zeigt an, wo Sie Fehler gemacht haben, und Sie wissen, wo Sie Elemente wie die Parameterüberprüfung hinzufügen müssen. Zur Entwicklungszeit ist dies großartig. Dieses Feature ist nicht so großartig, wenn Sie den Webdienst bereitstellen. Wenn Clients beginnen, den Webdienst zu verwenden, möchten Sie keine Dinge zurückgeben, z. B. welche Funktion in welchem Modul fehlgeschlagen ist. Dadurch werden Informationen über die Struktur des Codes offengelegt und können einem Angreifer dabei helfen, Ihren Webdienst zu unterbrechen.

Was tun Sie also, wenn Sie den Webdienst bereitstellen? Sie müssen Web.config öffnen und das Attribut /configuration/system.web/customErrors/@mode bearbeiten. Standardmäßig ist dieses Attribut auf RemoteOnly festgelegt. Das Mode-Attribut akzeptiert zwei weitere Werte:

  • Ein
  • Deaktiviert

Die Einstellung On weist ASP.NET an, die Stapelablaufverfolgungsinformationen nicht zu Fehlern hinzuzufügen. RemoteOnly bewirkt, dass die zusätzlichen Informationen für Clients auf demselben Server angezeigt werden und die Informationen für Remotebenutzer ausgeblendet werden. Wenn sie auf Aus festgelegt ist, werden die zusätzlichen Informationen für alle Aufrufe des Webdiensts angezeigt. Dies ist das gleiche Verhalten, das Sie auch für Webseiten sehen. Wenn Sie das Modus-Attribut auf Ein festlegen und dieselbe Ausnahme auslösen, erhalte ich stattdessen den folgenden Fehler:

<soap:Fault>
    <faultcode>soap:Server</faultcode>    
    <faultstring>Server was unable to process request. --&gt; Something 
bad happened</faultstring>
    <detail />
</soap:Fault>

An diesem Punkt gibt ich denselben Fehler ohne Informationen darüber zurück, wo die Ausnahme generiert wurde. Das gefällt mir, weil ich niemandem die Struktur meines Codes enthülle. Wenn ich die vom internen Code generierte Ausnahme zurückgebe, könnte ich dem Aufrufer immer noch interessante Tidbits enthüllen. Wenn der Webdienst beispielsweise auf eine Datenbank zugreift, können viele Ausnahmen die Benutzeridentität auf die Datenbank sowie die Tabellenstruktur anzeigen. Wenn ein Angreifer die Tabellenstruktur kennt, kann er versuchen, einige Verwüstungen anzurichten. Durchschnittliche Benutzer interessieren sich möglicherweise nicht dafür, dass ein Überlauf aufgetreten ist oder dass auf die Datenbank nicht zugegriffen werden konnte. Sie wären wahrscheinlich zufriedener mit einem Rückgabewert, der einfach besagt, dass der Server die Anforderung nicht verarbeiten konnte. Dazu können Sie die SoapException-Klasse verwenden. Diese Klasse erhält eine spezielle Behandlung und kann weitere Informationen ausblenden, die dem Client nicht wichtig sind (und häufig nicht wissen sollten). Betrachten Sie eine Funktion wie die folgende:

<WebMethod()> _
Public Sub CallFault()
    Try
        ' Deliberately throw an exception here
        Throw New Exception("Throwing a fault at " + _
            DateTime.Now.ToString())
    Catch ex As Exception
        Throw New SoapException( _
            "Server was unable to process request.", _
            SoapException.ServerFaultCode)
    End Try
End Sub

Der obige Code löst immer eine Ausnahme aus. Dies kann genauso einfach der Zugriff auf eine Datenbank, das Lesen einer Datei oder das Abrufen von Daten über das Netzwerk sein. Hier geht es darum, dass der Webdienst den Aufrufer nicht über intern generierte Ausnahmen informiert. Stattdessen teilt der Dienst dem Client mit, dass etwas Schlechtes passiert ist, und gibt zurück. Da eine Ausnahme abgefangen und eine SoapException ausgelöst wird, wird die Fehlerzeichenfolge auf Folgendes reduziert:

<faultstring>Server was unable to process request.</faultstring>

Es empfiehlt sich zwar, den Grund zu ausblenden, warum der Server die Anforderung nicht verarbeiten konnte (Sie werden keine Informationen über die interne Struktur Ihrer Anwendung preisgeben), aber Sie möchten wahrscheinlich den tatsächlichen Grund speichern, aus dem der Fehler an einem bestimmten Speicherort ausgelöst wurde. Zu den Orten zum Protokollieren von Problemen gehören eine Datenbank, Ereignisprotokolle und die Ablaufverfolgung der Webanwendung. Von diesen drei ist für Ihre Anwendung immer nur eine verfügbar: die Anwendungsablaufverfolgung. Wenn die Datenbank aufgrund von Netzwerk-/Sicherheitsproblemen nicht erreichbar ist oder die Anwendung nicht über die erforderlichen Berechtigungen zum Anmelden in einem Ereignisprotokoll verfügt, können Sie die Ausnahme möglicherweise nie erfassen. Die Anwendungsablaufverfolgung hat eigene Probleme. Insbesondere enthält es nur eine endliche Anzahl von Ablaufverfolgungsanweisungen, und diese Anweisungen können verschwinden, wenn die Webanwendung aus irgendeinem Grund zurückgesetzt oder gezyklust wird. Daher empfiehlt es sich, die Nachrichten an zwei Speicherorten zu protokollieren: einer Datenbanktabelle oder einem Ereignisprotokoll und der Anwendungsablaufverfolgung.

Damit CallFault die Anwendungsablaufverfolgung verwendet, habe ich sie wie folgt bearbeitet:

<WebMethod()> _
Public Sub CallFault()
    Try
        ' Deliberately throw an exception here
        Throw New Exception("Throwing a fault at " + _
            DateTime.Now.ToString())
    Catch ex As Exception
        Me.Context.Trace.Write(ex.ToString())
        Throw New SoapException( _
            "Server was unable to process request.", _
            SoapException.ServerFaultCode)
    End Try
End Sub

Damit die Ablaufverfolgung funktioniert, muss für das Element /configuration/system.web/trace in Web.config das attribut enabled auf true festgelegt sein. Ich empfehle, die restlichen Elemente allein zu lassen. Für einen häufig verwendeten Webdienst können Sie auch den Wert des attributs requestLimit erhöhen, damit die Ablaufverfolgung weitere Werte beibehalten kann. Dieser bestimmte Webdienst wird in einer Webanwendung namens AYS17Sept2002 bereitgestellt. Um die Ablaufverfolgungsmeldungen anzuzeigen, navigiere ich zur URL. https://localhost/AYS17Sept2002/Trace.axd. Nachdem sie eine Anwendung ausgeführt haben, die den Webdienst 10 Mal aufruft, weist die Anwendungsablaufverfolgung 10 Einträge auf. Wenn ich einen Eintrag auswählt, erhalte ich viele Informationen darüber, was während der Ausführung des Webdiensts passiert ist. Der Ablaufverfolgungsabschnitt ist für mich von besonderem Interesse. Es enthält die folgenden Informationen:

Abbildung 1. Die Ablaufverfolgungsinformationen für die erfasste Ausnahme

Dies fasst ziemlich genau zusammen, wie faultmessage verwendet wird und wie das Element vom Server verwendet wird. Der nächste interessante Punkt ist der Fehlercode. Nicht alle Probleme treten auf dem Server auf, aber der Server kann eine Reihe von ihnen erkennen. Für diese Situationen ist es wirklich wichtig anzugeben, wer das Problem verursacht hat, damit es behoben werden kann.

Fehler/Fehlercode

Bei einer Webdienstnachricht können viele Fehler auftreten. Auf dem Server tritt möglicherweise ein Problem auf, die Eingabedaten sind möglicherweise falsch, oder es kann ein Header auftreten, den der Server nicht versteht. Der Fehlercode kann einen der folgenden Werte aufweisen:

  • VersionMismatch: Der SOAP-Empfänger sah einen Namespace, der dem SOAP-Umschlag zugeordnet ist, den er nicht erkennt. Wenn dieser Fehlercode empfangen wird, sollte die Nachricht nicht erneut gesendet werden. Der SOAP-Namespace muss auf etwas festgelegt werden, das der Empfänger versteht.
  • MustUnderstand: Ein unmittelbar untergeordnetes Element des SOAP-Headers wurde mustUnderstand auf true festgelegt. Der Empfänger der Nachricht hat den Header nicht verstanden. Der Empfänger muss irgendwie aktualisiert werden (neuer Code, neue Bibliotheken usw.), um den Header zu verstehen.
  • Client: Etwas über die Art und Weise, wie die Nachricht formatiert wurde oder die darin enthaltenen Daten falsch waren. Der Client muss seinen Fehler beheben, damit die Nachricht zurückgesendet wird. Wenn Sie diesen Fehlercode zurückgeben, sollten Sie auch das detail-Element mit einigen Details dazu füllen, was passieren muss, damit die Nachricht durchgeht.
  • Server: Auf dem Server ist ein Fehler aufgetreten. Abhängig von der Art des Fehlers können Sie möglicherweise genau dieselbe Nachricht erneut an den Server senden und sehen, wie sie verarbeitet wird. Natürlich benötigt der Server selbst möglicherweise ein Update. Wenn z. B. eine Datenbankverbindungszeichenfolge falsch ist, führt kein erneuter Senden der Nachricht zu einem Erfolg.

Wenn eine ausnahme als System.Web.Services.Protocols.SoapException aus dem Code ausgelöst wird, der vom Serverseite des Webdiensts ausgeführt wird, legt ASP.NET den Fehlercode auf Server fest. Von den oben genannten Fehlern erlauben Sie in der Regel, dass ASP.NET die Überprüfung der Meldung auf die Fehlercodes VersionMismatch und MustUnderstand verarbeiten. VersionMismatch wird abgefangen, wenn der SOAP Envelope-Namespace nicht mit den Erwartungen der Umgebung übereinstimmt. Ein MustUnderstand-Fehler wird immer dann gesendet, wenn ein untergeordnetes Header-Element mit dem mustUnderstand-Attribut auf true oder 1 festgelegt ist und nicht verstanden wird. In den meisten WebMethod-basierten Webdiensten wird der Header vom SoapHeaderAttribute behandelt. Dieses Attribut ordnet ein öffentliches Element der Webdienstklasse einem eingehenden untergeordneten Headerelement zu. Wenn ein untergeordnetes Headerelement angezeigt wird, sucht ASP.NET in der Zielmethode nach dem SoapHeaderAttribute-Attribut . Wenn keine für die jeweilige Zuordnung gefunden wird und mustUnderstandtrue oder 1 ist, gibt der Webdienst einen MustUnderstand-Fehlercode zurück.

Alle Fehlercodewerte werden in der SoapException-Klasse als statische Klassenmember gespeichert. Sie lauten wie folgt:

  • VersionMismatchFaultCode
  • MustUnderstandFaultCode
  • ClientFaultCode
  • ServerFaultCode

Beim Schreiben eines Webdiensts, der eine SoapException auslöst, sollten Sie eigentlich nur Fehler auslösen, die clientFaultCode oder ServerFaultCode verwenden. Die anderen beiden werden normalerweise von ASP.NET behandelt und ausgelöst. Im Allgemeinen sollten Sie ClientFaultCode für Fehler verwenden, die in den an Sie gesendeten Daten erkannt wurden. Die Werte sind möglicherweise ungültig oder fehlen. Der Benutzer kann auch einen Wert übergeben, der zum Suchen eines bestimmten Datensatzes bestimmt ist. Wenn der Wert nicht auf ein vorhandenes Element verweist, können Sie auch einen Fehler zurückgeben. Denken Sie daran, dass es möglicherweise angebracht ist, nichts zurückzugeben. Sehen wir uns an, wie Sie die Überprüfung für einen Webdienst durchführen können, der einige grundlegende Benutzerinformationen akzeptiert. Die Klasse, die sie einnimmt, sieht wie folgt aus:

Public Class PersonData
    Public FirstName As String
    Public LastName As String
    Public EmailAddress As String
End Class

Es wäre zwar am besten, die Überprüfung in PersonData durch Hinzufügen benutzerdefinierter Eigenschaften durchzuführen, aber in diesem erfundenen Beispiel wird die Überprüfung im Webdienstcode durchgeführt. Mithilfe der Auswertung regulärer Ausdrücke werden die Daten überprüft, und ein entsprechender Fehler wird zurückgegeben.

<WebMethod()> _
Public Sub StoreData(ByVal pd As PersonData)

    ' Check names using regular expressions
    Dim nameRegEx As New Regex("^([A-Z])([a-z])+")
    Dim emailRegEx As New Regex("^([a-zA-Z0-9_\-\.]+)@" & _
        "((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([" & _
        "a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$")
    If Not (nameRegEx.IsMatch(pd.FirstName)) Then
        Throw New SoapException("Invalid First Name", _
            SoapException.ClientFaultCode)
    ElseIf Not (nameRegEx.IsMatch(pd.LastName)) Then
        Throw New SoapException("Invalid Last Name", _
            SoapException.ClientFaultCode)
    ElseIf Not (emailRegEx.IsMatch(pd.EmailAddress)) Then
        Throw New SoapException("Invalid e-mail address", _
            SoapException.ClientFaultCode)
    End If

    ' Do something to store the data
End Sub

Übrigens habe ich den regulären E-Mail-Ausdruck nicht selbst gekocht. Ich habe diesen unter http://www.regxlib.com/Default.aspxgefunden. Es behandelt viele Sonderfälle, an die ich nicht gedacht habe, als ich zum ersten Mal versucht habe, den Ausdruck selbst zu erstellen.

Dieser Validierungscode gibt an, wie Sie eine eigene Parametervalidierung für Ihren eigenen Webdienst erstellen möchten. Wenn Sie einen Fehler zurückgeben, sollten Sie auch zusätzliche Details angeben. Verwenden Sie dazu das Fault/detail-Element.

Fehler/Detail

Wenn Sie einen Clientfehler haben, sollten Sie dem Client erklären, warum der Fehler aufgetreten ist und wie er seinen eigenen Code beheben konnte. Um diese Informationen hinzuzufügen, verfügt der Fehler über ein optionales Detailelement. Dieses Element kann ein beliebiges gültiges XML-Element enthalten und nur verwendet werden, wenn der Fehler etwas beschreibt, das im SOAP-Text aufgetreten ist. Dies ist das einzige Element, das nicht sowohl mit der SoapException - als auch mit der SoapHeaderException-Klasse festgelegt werden kann. Dies schließt nur alten Text ein. Um zu zeigen, wie dieses Element verwendet wird, dachte ich, dass es interessant sein könnte, alle Elemente in der PersonData-Klasse zu überprüfen und einen Fehler zurückzugeben, wenn Elemente ungültig sind. Dadurch werden Informationen zu allen ungültigen Elementen zurückgegeben. Um dies zu behandeln, habe ich eine Enumeration erstellt, die verwendet werden kann, um das ungültige Element zu identifizieren.

Public Enum PersonDataItem
    FirstName
    LastName
    EmailAddress
End Enum

Dann habe ich eine kleine Klasse erstellt, die ich verwenden kann, um zu erklären, was mit dem Element falsch ist. Da ich reguläre Ausdrücke zum Überprüfen der Eingabe verwende, warum nicht einfach einen regulären Ausdruck senden, um das richtige Format anzuzeigen?

Public Class PersonErrorInfo
    Public ItemInError As PersonDataItem
    Public CorrectRegularExpression As String
End Class

Warum habe ich eine spezielle Klasse erstellt? Ich möchte die XmlSerializer-Klasse und ihre Fähigkeit verwenden können, die Klasse automatisch zu serialisieren. Der Fehlerüberprüfungscode sieht nun in etwa wie folgt aus:

<WebMethod()> _
Public Sub StoreData(ByVal pd As PersonData)

    ' Check names using regular expressions
    Dim nameRegEx As New Regex("^([A-Z])([a-z])+")
    Dim emailRegEx As New Regex("^([a-zA-Z0-9_\-\.]+)@" & _
        "((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([" & _
        "a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$")
    Dim errorDetails(3) As PersonErrorInfo
    Dim errorIndex As Integer = 0

    ' Check the information and see if it is valid.
    ' If not, add an entry to the errorDetails array.
    If Not (nameRegEx.IsMatch(pd.FirstName)) Then
        errorDetails(errorIndex) = New PersonErrorInfo()
        errorDetails(errorIndex).ItemInError = PersonDataItem.FirstName
        errorDetails(errorIndex).CorrectRegularExpression = _
            nameRegEx.ToString()
        errorIndex = errorIndex + 1
    End If
    If Not (nameRegEx.IsMatch(pd.LastName)) Then
        errorDetails(errorIndex) = New PersonErrorInfo()
        errorDetails(errorIndex).ItemInError = PersonDataItem.LastName
        errorDetails(errorIndex).CorrectRegularExpression = _
            nameRegEx.ToString()
        errorIndex = errorIndex + 1
    End If
    If Not (emailRegEx.IsMatch(pd.EmailAddress)) Then
        errorDetails(errorIndex) = New PersonErrorInfo()
        errorDetails(errorIndex).ItemInError = _
            PersonDataItem.EmailAddress
        errorDetails(errorIndex).CorrectRegularExpression = _
            emailRegEx.ToString()
        errorIndex = errorIndex + 1
    End If

    ' If we found an error, prepare and send the SOAP Fault.
    If (errorIndex > 0) Then

        ' Set up the serialization items
        Dim ser As New XmlSerializer(GetType(PersonErrorInfo))
        Dim stm As New MemoryStream()
        Dim utfEnc As New UTF8Encoding()
        Dim xmlw As New XmlTextWriter(stm, System.Text.Encoding.UTF8)
        Dim index As Integer

        ' Write the start element to the stream
        xmlw.WriteStartElement("detail")

        ' Create the serialization of the details
        For index = 0 To errorIndex - 1
            ser.Serialize(xmlw, errorDetails(index))
        Next

        ' Close the last tag and flush the contents
        xmlw.WriteEndElement()
        xmlw.Flush()

        ' Create a new document to write the stream to.
        Dim doc As New XmlDocument()
        Dim xmlStr As String = utfEnc.GetString(stm.GetBuffer())

        ' The string contains a byte string that indicates the
        ' byte order. Delete it right away.
        xmlStr = xmlStr.Substring(1)
        System.Diagnostics.Trace.WriteLine(xmlStr)
        doc.LoadXml(xmlStr)

        ' Were done with the stream, so close it.
        xmlw.Close()

        ' Send the fault
        Throw New SoapException("Invalid input", _
            SoapException.ClientFaultCode, _
            Context.Request.Url.ToString(), _
            doc.DocumentElement)
    End If

End Sub

Wenn für alle Elemente ein Fehler auftritt, sieht die Ausgabemeldung wie folgt aus:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope 
    xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <soap:Fault>
      <faultcode>soap:Client</faultcode>
      <faultstring>Invalid input</faultstring>
      <faultactor
      >http://sseely2/AYS17Sept2002/Service1.asmx</faultactor>
      <detail>
        <PersonErrorInfo 
            xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
          <ItemInError>FirstName</ItemInError>
          <CorrectRegularExpression
          >^([A-Z])([a-z])+</CorrectRegularExpression>
        </PersonErrorInfo>
        <PersonErrorInfo 
            xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
          <ItemInError>LastName</ItemInError>
          <CorrectRegularExpression
          >^([A-Z])([a-z])+</CorrectRegularExpression>
        </PersonErrorInfo>
        <PersonErrorInfo 
            xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
          <ItemInError>EmailAddress</ItemInError>
          <CorrectRegularExpression
          >^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-
9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})
(\]?)$</CorrectRegularExpression>
        </PersonErrorInfo>
      </detail>
    </soap:Fault>
  </soap:Body>
</soap:Envelope>

Das ist so ziemlich alles, was Sie über die Verwendung eines speziellen Detailelements wissen müssen. Für einen Client, der einen solchen Fehler mit einem benutzerdefinierten Detailelement empfängt, kann dieser Client das Detailelement durchlaufen oder es in einen internen Typ serialisieren. Hier ist ein Beispielcode, der eine etwas besser lesbare Version des Fehlers für Benutzer generiert, die XML nicht lesen möchten.

Zunächst verfügen wir über eine Funktion, die den StoreData-Webdienst aufruft:

Sub CallStoreData()
    Dim svc As New localhost.Service1()
    Console.WriteLine("Calling StoreData")
    Try
        Dim pd As New localhost.PersonData()
        pd.EmailAddress = "sseely@microsoft.com"

        ' The following line should generate an error.
        ' The first letter of each name must be 
        ' capitalized.
        pd.FirstName = "scott"
        pd.LastName = "Seely"
        svc.StoreData(pd)
    Catch ex As SoapException
        DisplayNode(0, ex.Detail)
    Catch ex As Exception
        Console.WriteLine("Unexpected exception: " & ex.ToString())
    Finally
        If Not (svc Is Nothing) Then
            svc.Dispose()
        End If
    End Try
End Sub

Wenn eine SoapException empfangen wird, ruft der Code DisplayNode auf, um das Detailelement besser lesbar anzuzeigen:

Sub DisplayNode(ByVal displayDepth As Integer, _
    ByVal theNode As XmlNode)
    Dim indentString As String = _
        New String(ChrW(32), 3 * displayDepth)
    Dim elem As XmlNode

    ' Don't display whitespace nodes.
    If (theNode.Name <> "#whitespace") Then
        Console.WriteLine(indentString & theNode.Name & _
            ": " & theNode.Value)
    End If

    ' Display the child nodes
    For Each elem In theNode.ChildNodes
        DisplayNode(displayDepth + 1, elem)
    Next
End Sub

Da der Clientcode in StoreData absichtlich ungültige Informationen herausgibt, werden die Fehlerinformationen angezeigt. Wenn ich den Client für den Webdienst aus führe, erhalte ich die folgende Ausgabe:

Calling StoreData
detail:
   PersonErrorInfo:
      ItemInError:
         #text: FirstName
      CorrectRegularExpression:
         #text: ^([A-Z])([a-z])+

Okay, das ist nicht sehr leicht zu verstehen, aber für viele Menschen ist es möglicherweise einfacher zu lesen als das gerade XML. Auf jeden Fall können Sie das zurückgegebene Detailelement durchkämmen und etwas erstellen, das für den Computer oder alle Menschen, die den Computer verwenden, sinnvoll ist.

Zusammenfassung

Als Entwickler von Webdiensten müssen Sie sich darauf konzentrieren, wie Fehlerinformationen an Clients zurückgegeben werden. Dinge, die für Sie als Entwickler hilfreich sind, können Details zu Ihrer Anwendung offenbaren, die Sie nach der Bereitstellung des Webdiensts möglicherweise nicht anzeigen möchten. Um die Sichtbarkeit zu behandeln, müssen Sie Ausnahmen erfassen, bevor sie von ASP.NET behandelt werden, damit Details nicht versehentlich verloren gehen. Die Sichtbarkeit wird behandelt, indem web.config entsprechend bearbeitet und Ausnahme ausgelöster Code in try/catch-Blöcke eingeschlossen wird.

Nachdem die Sichtbarkeit verarbeitet wurde, müssen Sie auch die Clienteingabe überprüfen. ASP.NET führt nur einfache Überprüfungen durch und transformiert den XML-Code in CLR-Typen. Nach Abschluss der Transformation müssen Sie möglicherweise eine zusätzliche Überprüfung durchführen. Als Ergebnis dieser Überprüfung legt der Code eine benutzerdefinierte Faultmessage fest und sendet einige Details an den Aufrufer, was falsch war.

 

Zu Ihren Diensten

Scott Seely ist Mitglied des MSDN Architectural Samples-Teams. Neben seiner Arbeit dort ist Scott autor von SOAP: Cross Platform Web Service Development Using XML (Prentice Hall –PTR) und hauptautor für Creating and Using Web Services in Visual Basic (Addison-Wesley).