In this chapter, you will learn how to:
Throw exceptions in XML Web services.
Handle exceptions thrown by XML Web services.
Use custom fault codes to better report errors in XML Web services.
Exceptions are a powerful programming technique, supported by both C# and Visual Basic .NET, that provides a structured approach to handling system and application error conditions. You can use exceptions within the scope of your XML Web services as you do in any other application. Only when an XML Web service throws an exception that must be propagated back to the client is special consideration required.
When an XML Web service throws an exception that it does not handle internally, the exception passes to ASP.NET. The action taken by ASP.NET depends on which protocol the client is using to communicate with the XML Web service.
Communicating Exceptions to HTTP Clients
If the client is using HTTP-GET or HTTP-POST, ASP.NET returns an HTTP Internal Server Error (Error code 500) to the client. The body of the error response contains a string representation of the exception’s stack trace. The client must determine the appropriate action to take in response to the error, but the only information it can base the decision on is the stack trace string. Parsing the string information is relatively expensive computationally and far from straightforward when compared to the normal process of exception handling.
Things are slightly better if the client is using a .NET HTTP proxy class. The proxy receives the HTTP error and raises a System.Net.WebException in response, avoiding the need to manipulate HTTP messages directly and enabling the use of normal exception handling techniques. The stack trace string is contained in the WebException.Message property, but the string still requires processing to determine the cause of the error.
Communicating Exceptions to SOAP Clients
If the client is using SOAP, ASP.NET catches the unhandled exception, encodes it as a SOAP Fault, and passes it back to the client. The SOAP specification defines a SOAP Fault as a mechanism for encapsulating and transmitting error information using SOAP messages. SOAP Faults consist of fields that contain information about the error that occurred. Although SOAP Faults do not provide a direct mapping for encoding the complete state of a .NET exception, they do provide more flexibility than the simple stack trace string available in the case of HTTP.
Two key SOAP Fault fields are faultcode and faultstring. The faultcode field identifies the fault that has occurred; by default, ASP.NET always sets this field to a value indicating a server error, regardless of the fact that the error could have been caused by bad data from the client. The faultstring field contains human readable information about the error. ASP.NET places a string representation of the caught exception’s stack trace in faultstring.
When a client using a SOAP proxy receives a SOAP Fault, it behaves in a manner similar to the HTTP proxy that we described earlier; however, the SOAP proxy raises a System.Web.Services.Protocols.SoapException. Additionally, the proxy sets the SoapException.Code property to the value of the SOAP Fault’s faultcode field and the SoapException.Message property to the value of faultstring. The following table shows SoapException to SOAP Fault mappings.
SoapException property | SOAP Fault element | Default contents |
---|---|---|
Code | faultcode | Server error |
Message | faultstring | String representation of caught exception’s stack trace |
Unfortunately, this mechanism so far is no better than the HTTP scenario discussed earlier. No matter what exception the XML Web service throws, the client proxy always throws a SoapException. Because the SoapException.Code property always indicates a generic server error, the client must still parse the stack trace string contained in the SoapException.Message property to determine the details of the exception thrown.
The way to overcome these issues is surprisingly simple. Instead of allowing ASP.NET to handle uncaught client-bound exceptions automatically, your XML Web service must capture all application exceptions and explicitly throw a SoapException. When ASP.NET catches a SoapException, it correctly fills in the SOAP Fault faultcode and faultstring elements using the Code and Message properties of the SoapException. This enables you to return custom fault codes that provide meaningful error information to the client application. The client can then catch the SoapException and perform the appropriate action by using the fault code provided by the SoapException.Code property.
One disadvantage of using custom fault codes is that developers of client applications must know, and explicitly handle, the codes. Because this information is not contained in the WSDL document for the XML Web service, it must be communicated in your system’s documentation.
Armed with an understanding of exceptions in XML Web services, SOAP Faults, and custom fault codes, we will add a more robust and informative error-handling solution to our credit card validation service in the procedures that follow. We will develop a new XML Web service based on the ValidatorService XML Web service that we created in Chapter 6.
We will modify the exception handling code that wraps our calls to methods contained in CreditCardValidator.dll. Instead of catching all exceptions and setting the validation result to false, we catch all ApplicationException exceptions that are thrown and generate a SoapException with a custom fault code. To set the custom fault code, we use a constructor of the SoapException class that lets us set the Code and Message properties. The only thing to be aware of is that the SoapException.Code property is not a String but an instance of System.Xml.XmlQualifiedName. You must instantiate an XmlQualifiedName based on the String you want to return.
The following table summarizes the exceptions the methods of the Validator class can throw and maps them to the custom fault codes we will assign. Full details of the Validator class and the CreditCardValidation.dll library are available in Appendix A.
Exception | Fault Code and Description |
---|---|
CCIllegalCharacterException | Fault code: Client.IllegalCharacter Thrown if the credit card number passed to the method contains illegal characters. Only decimal digits and white space are valid in credit card numbers. |
CCInvalidLengthException | Fault code: Client.InvalidLength Thrown if the credit card number passed to the method has the wrong number of digits for the type of card. |
CCInvalidPrefixException | Fault code: Client.InvalidPrefix Thrown if the credit card number passed to the method does not have the expected prefix for the card type. For example, all valid VISA cards must start with the digit 4. |
Procedure 7-1. Copy the XML Web Service Project
Use the Copy Project function in Visual Studio .NET to copy the ValidatorService XML Web service that you created in the section "Reference Arguments" in Chapter 6.
For this example, copy the ValidatorService project to http://localhost/XMLWebServices/Chapter07/CustomFaultCodes/ValidatorService/.
Ensure that you close the old project and open the new one before proceeding. Save the new solution files by selecting Save All from the File menu and choosing a suitable location.
Procedure 7-2. Add the Exception Handling Code
In Solution Explorer, right-click on the Validation.asmx file and select View Code from the shortcut menu.
In the Validation.asmx code view window, locate the ValidateCard method and replace it with this code.
The new ValidateCard method replaces the exception handling code that was associated with the credit card validation calls to CreditCardValidation.dll. Instead of catching all exceptions and simply setting p_object.o_valid to false for return to the client, we now catch the exception and throw a SoapException by calling the GetException method. We discuss the addition and purpose of the GetException method in step 3.
Example 7-1. C#
[WebMethod] public void ValidateCard(ref ValidationObject p_object) { // define a boolean to hold the result // of the validation request bool x_result; try { // create the validator instance that we will // use to check the card number Validator x_validator = new Validator(); // use the type of card supplied in the // validation object to determine which // library method we should call in order // to perform the validation switch (p_object.o_card_type) { case CARD_TYPE.AMEX: // the client has requested that we validate // an AMEX card number x_result = x_validator.ValidateAMEX( p_object.o_card_number); break; case CARD_TYPE.MASTERCARD: // the client has requested that we validate // a MasterCard card number x_result = x_validator.ValidateMasterCard( p_object.o_card_number); break; case CARD_TYPE.VISA: // the client has requested that we validate // a VISA card number x_result = x_validator.ValidateVisa( p_object.o_card_number); break; default: // the method should not reach this point; if it // does, then the client has specified // a value which is not contained in the CARD_TYPE // enumeration. Should this happen, the client has // requested that we validate a card type that we // do not support. x_result = false; break; } } catch (ApplicationException x_ex) { // return a soap exception with a custom fault // code based on the type of the application // exception throw GetException(x_ex); } // set the result status in the validation object p_object.o_valid = x_result; }
Example 7-2. Visual Basic .NET
<WebMethod()> _ Public Sub ValidateCard(ByRef p_object As ValidationObject) ’ define a boolean to hold the result ’ of the validation request Dim x_result As Boolean Try ’ create the validator instance that we will ’ use to check the card number Dim x_validator As Validator = New Validator() ’ use the type of card supplied in the ’ validation object to determine which ’ library method we should call in order ’ to perform the validation Select Case p_object.o_card_type Case CARD_TYPE.AMEX ’ the client has requested that we validate ’ an AMEX card number x_result = x_validator.ValidateAMEX(_ p_object.o_card_number) Case CARD_TYPE.MASTERCARD ’ the client has requested that we validate ’ a MasterCard card number x_result = x_validator.ValidateMasterCard(_ p_object.o_card_number) Case CARD_TYPE.VISA ’ the client has requested that we validate ’ a VISA card number x_result = x_validator.ValidateVisa(_ p_object.o_card_number) Case Else ’ the method should not reach this point; if it ’ does, then the client has specified ’ a value which is not contained in the CARD_TYPE ’ enumeration. Should this happen, the client has ’ requested that we validate a card type that we ’ do not support. x_result = False End Select Catch x_ex As ApplicationException ’ return a soap exception with a custom fault ’ code based on the type of the application ’ exception Throw GetException(x_ex) End Try ’ set the result status in the validation object p_object.o_valid = x_result End Sub
Add the new GetException method as a member of the ValidatorServiceClass class.
The GetException method takes an ApplicationException as an argument. The method instantiates a new SoapException object and sets the Code and Message properties of the exception depending on the type of exception it was passed.
Example 7-3. C#
private SoapException GetException(ApplicationException x_ex) { XmlQualifiedName x_name; if (x_ex is CCIllegalCharacterException) { x_name = new XmlQualifiedName("Client.IllegalCharacter"); } else if (x_ex is CCInvalidLengthException) { x_name = new XmlQualifiedName("Client.InvalidLength"); } else if (x_ex is CCInvalidPrefixException) { x_name = new XmlQualifiedName("Client.InvalidPrefix"); } else { x_name = SoapException.ServerFaultCode; } return new SoapException(x_ex.Message, x_name); }
Example 7-4. Visual Basic .NET
Private Function GetException(ByVal x_ex As ApplicationException) _ As SoapException Dim x_name As XmlQualifiedName If TypeOf x_ex Is CCIllegalCharacterException Then x_name = New XmlQualifiedName("Client.IllegalCharacter") ElseIf TypeOf x_ex Is CCInvalidLengthException Then x_name = New XmlQualifiedName("Client.InvalidLength") ElseIf TypeOf x_ex Is CCInvalidPrefixException Then x_name = New XmlQualifiedName("Client.InvalidPrefix") Else x_name = SoapException.ServerFaultCode End If Return New SoapException(x_ex.Message, x_name) End Function
Insert the following statements at the start of the Validation.asmx code view to import the SoapException and XmlQualifiedName classes.
Select Build Solution from the Build menu (or press Ctrl+Shift+B) to build the ValidatorService project.
We now have an XML Web service that uses exceptions and custom fault codes to notify clients of error conditions that occur during the validation of a credit card number. No longer can our client application simply process Boolean responses; it must also handle the exceptions that might occur in response to the validation requests we submit. In the procedures that follow, we’ll develop a new client application that handles exceptions and custom fault codes returned by the XML Web service it consumes.
Procedure 7-3. Create the WindowsFormsClient project
Create a new Visual Studio .NET project using either the C# or Visual Basic .NET Windows Application project template. In the New Project dialog box, name the project WindowsFormsClient and specify C:XMLWebServicesSBSProjectsChapter07CustomFaultCodes for the location.
Create a Web reference that points to the ValidatorService that we created earlier in this chapter, using the URL http://localhost/XMLWebServices/Chapter07/CustomFaultCodes/ValidatorService/Validation.asmx.
In Solution Explorer, right-click the Web reference and select Rename from the shortcut menu. Rename the Web Reference Validator.
Procedure 7-4. Create the Windows Form
In Solution Explorer, double-click the source file for Form1 (either Form1.cs or Form1.vb depending on which language you are using) to open the design view for Form1.
Choose Properties Window from the View menu (or press F4) to open the Properties window for Form1. Configure the properties as specified in the following table.
Property | Value |
---|---|
Text | Validator Client |
Size | 288, 208 |
Use the Visual Studio .NET toolbox to add four labels, a button, a combo box, and a text box to Form1. The form’s user interface is the same as in examples in previous chapters.
Configure the controls’ properties as shown in the following table.
Control | Property | Value |
---|---|---|
Label | Text | Card Type: |
TextAlign | MiddleRight | |
Location | 8, 8 | |
Label | Text | Card Number: |
TextAlign | MiddleRight | |
Location | 8, 40 | |
Label | Text | Result: |
TextAlign | MiddleRight | |
Location | 8, 80 | |
ComboBox | Text | VISA |
Items | AMEX MasterCard VISA | |
Name | CardType | |
Location | 144, 8 | |
Size | 128, 21 | |
TextBox | Text | Delete the contents of the Text property value. |
Name | CardNumber | |
Location | 144, 40 | |
Size | 128, 20 | |
Label | Text | Delete the contents of the Text property value. |
TextAlign | MiddleLeft | |
Name | Result | |
Location | 144, 80 | |
Size | 128, 23 | |
Button | Text | Validate |
Name | ValidateButton | |
Location | 109, 128 |
Procedure 7-6. Implement the WindowsFormsClient functionality
In the Form1 design view window, double-click on the Validate button.
This will display Form1 in code view and show the ValidateButton_Click method.
Replace the empty ValidateButton_Click method with the following code.
This code is slightly different from that which we have used in previous examples. (The differences are highlighted in bold.) The changes cause the client to catch any SoapException exceptions thrown by the ValidatorService.ValidateCard XML Web service method. On catching an exception, the client displays the appropriate message based on the known values of the SoapException.Code property.
Example 7-7. C#
private void ValidateButton_Click(object sender, System.EventArgs e) { // create the ValidationObject that we will send to // the XML Web service ValidationObject x_object = new ValidationObject(); // set the card type for the validation object based // on the user selection in the CardType ComboBox switch (CardType.Text) { case "AMEX": x_object.o_card_type = CARD_TYPE.AMEX; break; case "MasterCard": x_object.o_card_type = CARD_TYPE.MASTERCARD; break; case "VISA": x_object.o_card_type = CARD_TYPE.VISA; break; } // set the card number value in the ValidationObject x_object.o_card_number = CardNumber.Text; try { // request validation from the XML Web service // via the proxy class new ValidatorService().ValidateCard(ref x_object); // set the text of the result label based // on the response from the XML Web service if (x_object.o_valid) { Result.Text = "Number Valid"; } else { Result.Text = "Number Invalid"; } } catch (System.Web.Services.Protocols.SoapException x_ex) { // display the content of the exception switch (x_ex.Code.ToString()) { case "Client.IllegalCharacter": Result.Text = "Illegal Character"; break; case "Client.InvalidLength": Result.Text = "Invalid Length"; break; case "Client.InvalidPrefix": Result.Text = "Invalid Prefix"; break; default: Result.Text = "Unexpected Error"; break; } } }
Example 7-8. Visual Basic .NET
Private Sub ValidateButton_Click(ByVal sender As Object, ByVal e _ As System.EventArgs) Handles ValidateButton.Click ’ create the ValidationObject that we will send to ’ the XML Web service Dim x_object As ValidationObject = New ValidationObject() ’ set the card type for the validation object based ’ on the user selection in the CardType ComboBox Select Case CardType.Text Case "AMEX" x_Object.o_card_type = CARD_TYPE.AMEX Case "MasterCard" x_Object.o_card_type = CARD_TYPE.MASTERCARD Case "VISA" x_Object.o_card_type = CARD_TYPE.VISA End Select ’ set the card number value in the ValidationObject x_Object.o_card_number = CardNumber.Text Try ’ request validation from the XML Web service ’ via the proxy class Dim x_service As ValidatorService = New ValidatorService() x_service.ValidateCard(x_object) ’ set the text of the result label based ’ on the response from the XML Web service If x_Object.o_valid Then Result.Text = "Number Valid" Else Result.Text = "Number Invalid" End If Catch x_ex As System.Web.Services.Protocols.SoapException Select Case x_ex.Code.ToString() Case "Client.IllegalCharacter" Result.Text = "Illegal Character" Case "Client.InvalidLength" Result.Text = "Invalid Length" Case "Client.InvalidPrefix" Result.Text = "Invalid Prefix" Case Else Result.Text = "Unexpected Error" End Select End Try End Sub
Scroll to the top of the Form1 code view window and add the statements to import the proxy class namespace. Also add a statement to import the System.Web.Services.Protocols namespace. This statement gives the code access to the SoapException class.
Choose Build Solution from the Build menu (or press Ctrl+Shift+B) to build WindowsFormsClient.
Knowing the exceptions thrown by the Validator class in the CreditCardValidator.dll library, we can do three things to force it to throw an exception:
Pass a credit card with nondecimal characters in it
Pass a credit card number with an invalid length for the type of card specified
Pass a credit card number that has an invalid prefix for the type of card specified
Testing these conditions is simply a matter of putting incorrect data into the client application and clicking the Validate button. In the following procedure, we will force a CCIllegalCharacterException exception at the XML Web service by passing nondecimal characters as the credit card number.
Procedure 7-7. Test Error Conditions
While in Visual Studio .NET, press Ctrl+F5 to run the WindowsFormsClient application.
Select VISA as the card type.
Enter the text baddata as the card number; this will cause an exception because it contains nondecimal characters.
Click Validate. The result will be "Illegal Character," as shown in the following screen shot.
To | Do This |
---|---|
Throw an exception in an XML Web service | Throw the exception as you would normally. ASP.NET will catch it and return an error message to the client if it is not handled by the XML Web service internally. |
Handle an exception thrown by an XML Web service when developing an HTTP proxy client | Use standard exception handling syntax and catch exceptions of type System.Net.WebException. Parse the exception stack trace in the WebException.Message property to determine the appropriate action to take. |
Handle an exception thrown by an XML Web service when developing a SOAP proxy client | Use standard exception handling syntax and catch exceptions of type System.Web.Services.Protocols.SoapException. If the XML Web service uses custom fault codes, inspect the SoapException.Code property to determine the appropriate action to take. Otherwise, parse the exception stack trace in the SoapException.Message property to determine the appropriate action. |
Throw a specific exception and pass it back to the client. | This cannot be done. The best you can do is throw a SoapException. The values you set for the Code and Message properties of the SoapException are passed to the client and made available through the SoapException raised by the client proxy. |
3.15.18.198